diff --git a/CMakeLists.txt b/CMakeLists.txt
index c23f0c62e..1d5331fec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,13 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-project(keepassx)
+project(KeePassX)
cmake_minimum_required(VERSION 2.6.0)
-set( keepassx_VERSION "0.9.0" )
+option(WITH_TESTS "Enable building of unit tests" ON)
-add_Definitions(-D'KEEPASSX_VERSION="${keepassx_VERSION}"')
+set( KEEPASSX_VERSION "0.9.0" )
+
+add_definitions(-DQT_NO_KEYWORDS -Wall)
if( APPLE OR MINGW )
set( PROGNAME KeePassX )
@@ -27,10 +29,19 @@ else( APPLE OR MINGW )
set( PROGNAME keepassx )
endif( APPLE OR MINGW )
-set(QT_MIN_VERSION "4.3.0")
-set(QT_USE_QTXML TRUE)
+if( WITH_TESTS )
+ enable_testing()
+ set(QT_USE_QTTEST TRUE)
+endif( WITH_TESTS )
+
+set(QT_MIN_VERSION "4.6.0")
set(QT_USE_QTMAIN TRUE)
find_package(Qt4 REQUIRED)
include(${QT_USE_FILE})
+find_package(Automoc4 REQUIRED)
+
add_subdirectory(src)
+if( WITH_TESTS )
+ add_subdirectory(tests)
+endif( WITH_TESTS )
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 46d6d0fb5..13cf4f2f1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,8 +13,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
+
+configure_file( config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h )
+
set(keepassx_SOURCES
- main.cpp
core/Database.cpp
core/DbAttribute.cpp
core/Entry.cpp
@@ -25,18 +28,7 @@ set(keepassx_SOURCES
core/Uuid.cpp
)
-set(keepassx_HEADERS
- core/Database.h
- core/DbAttribute.h
- core/Entry.h
- core/Group.h
- core/Metadata.h
- core/Parser.h
- core/TimeInfo.h
- core/Uuid.h
-)
+automoc4_add_library( keepassx_core STATIC ${keepassx_SOURCES} )
-qt4_wrap_cpp( keepassx_MOC ${keepassx_HEADERS} )
-
-add_executable( ${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES} ${keepassx_MOC} )
-target_link_libraries( ${PROGNAME} ${QT_LIBRARIES} )
+add_executable( ${PROGNAME} WIN32 MACOSX_BUNDLE main.cpp )
+target_link_libraries( ${PROGNAME} keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} )
diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake
new file mode 100644
index 000000000..f79dde174
--- /dev/null
+++ b/src/config-keepassx.h.cmake
@@ -0,0 +1,3 @@
+/* config-keepassx.h. Generated by cmake from config-keepassx.h.cmake */
+
+#define KEEPASSX_VERSION "${KEEPASSX_VERSION}"
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 5045642c3..cd2c50c68 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -20,12 +20,12 @@
#include
#include
+#include "Metadata.h"
#include "Parser.h"
-Database::Database(const QString& filename)
+Database::Database()
{
- m_filename = filename;
-
+ m_metadata = new Metadata(this);
}
Group* Database::rootGroup()
@@ -35,8 +35,8 @@ Group* Database::rootGroup()
void Database::setRootGroup(Group* group)
{
+ Q_ASSERT(group == 0 || group->parent() == this);
m_rootGroup = group;
- group->setParent(this);
}
Metadata* Database::metadata()
@@ -44,12 +44,6 @@ Metadata* Database::metadata()
return m_metadata;
}
-void Database::open()
-{
- Parser* parser = new Parser(this);
- parser->parse(m_filename);
-}
-
QImage Database::icon(int number)
{
// TODO implement
diff --git a/src/core/Database.h b/src/core/Database.h
index e196b9e06..f89e083d0 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -32,7 +32,7 @@ class Database : public QObject
Q_OBJECT
public:
- Database(const QString& filename);
+ Database();
Group* rootGroup();
void setRootGroup(Group* group);
Metadata* metadata();
@@ -42,11 +42,9 @@ public:
Group* resolveGroup(const Uuid& uuid);
private:
- void open();
Entry* recFindEntry(const Uuid& uuid, Group* group);
Group* recFindGroup(const Uuid& uuid, Group* group);
- QString m_filename;
Metadata* m_metadata;
Group* m_rootGroup;
QHash m_customIcons;
diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp
index 4e8d2d3f8..b5e87a051 100644
--- a/src/core/Entry.cpp
+++ b/src/core/Entry.cpp
@@ -19,8 +19,9 @@
#include "Group.h"
-Entry::Entry() : m_group(0)
+Entry::Entry()
{
+ m_group = 0;
}
Uuid Entry::uuid() const
@@ -31,6 +32,7 @@ Uuid Entry::uuid() const
QImage Entry::icon() const
{
// TODO implement
+ return QImage();
}
QColor Entry::foregroundColor() const
@@ -85,6 +87,7 @@ const QHash& Entry::attachments() const
void Entry::setUuid(const Uuid& uuid)
{
+ Q_ASSERT(!uuid.isNull());
m_uuid = uuid;
}
@@ -153,7 +156,7 @@ void Entry::addAttachment(const QString& key, const QByteArray& value)
void Entry::setGroup(Group* group)
{
if (m_group) {
- group->removeEntry(this);
+ m_group->removeEntry(this);
}
group->addEntry(this);
m_group = group;
diff --git a/src/core/Group.cpp b/src/core/Group.cpp
index 51ab94493..478aa0884 100644
--- a/src/core/Group.cpp
+++ b/src/core/Group.cpp
@@ -22,8 +22,10 @@
#include "Database.h"
-Group::Group() : m_parent(0)
+Group::Group()
{
+ m_parent = 0;
+ m_db = 0;
}
Uuid Group::uuid() const
@@ -118,23 +120,38 @@ void Group::setLastTopVisibleEntry(Entry* entry)
void Group::setParent(Group* parent)
{
+ Q_ASSERT(parent != 0);
+
if (m_parent) {
m_parent->m_children.removeAll(this);
}
+ else if (m_db) {
+ m_db->setRootGroup(0);
+ }
+ m_parent = parent;
m_db = parent->m_db;
QObject::setParent(parent);
+
+ parent->m_children << this;
}
void Group::setParent(Database* db)
{
- if (m_db) {
+ Q_ASSERT(db != 0);
+
+ if (m_parent) {
+ m_parent->m_children.removeAll(this);
+ }
+ else if (m_db) {
m_db->setRootGroup(0);
}
m_parent = 0;
m_db = db;
QObject::setParent(db);
+
+ db->setRootGroup(this);
}
QList Group::children() const
diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp
index 2f967b003..ec9a15c98 100644
--- a/src/core/Metadata.cpp
+++ b/src/core/Metadata.cpp
@@ -17,8 +17,16 @@
#include "Metadata.h"
-Metadata::Metadata()
+#include "Database.h"
+
+Metadata::Metadata(Database* parent) : QObject(parent)
{
+ m_generator = "KeePassX";
+ m_maintenanceHistoryDays = 365;
+ m_recycleBin = 0;
+ m_entryTemplatesGroup = 0;
+ m_lastSelectedGroup = 0;
+ m_lastTopVisibleGroup = 0;
}
QString Metadata::generator() const
@@ -101,9 +109,9 @@ bool Metadata::recycleBinEnabled() const
return m_recycleBinEnabled;
}
-Uuid Metadata::recycleBinUuid() const
+const Group* Metadata::recycleBin() const
{
- return m_recycleBinUuid;
+ return m_recycleBin;
}
QDateTime Metadata::recycleBinChanged() const
@@ -111,7 +119,7 @@ QDateTime Metadata::recycleBinChanged() const
return m_recycleBinChanged;
}
-Uuid Metadata::entryTemplatesGroup() const
+const Group* Metadata::entryTemplatesGroup() const
{
return m_entryTemplatesGroup;
}
@@ -121,12 +129,12 @@ QDateTime Metadata::entryTemplatesGroupChanged() const
return m_entryTemplatesGroupChanged;
}
-Uuid Metadata::lastSelectedGroup() const
+const Group* Metadata::lastSelectedGroup() const
{
return m_lastSelectedGroup;
}
-Uuid Metadata::lastTopVisibleGroup() const
+const Group* Metadata::lastTopVisibleGroup() const
{
return m_lastTopVisibleGroup;
}
@@ -208,6 +216,7 @@ void Metadata::setAutoEnableVisualHiding(bool value)
void Metadata::addCustomIcon(const Uuid& uuid, const QImage& image)
{
+ Q_ASSERT(!uuid.isNull());
Q_ASSERT(!m_customIcons.contains(uuid));
m_customIcons.insert(uuid, image);
@@ -215,6 +224,7 @@ void Metadata::addCustomIcon(const Uuid& uuid, const QImage& image)
void Metadata::removeCustomIcon(const Uuid& uuid)
{
+ Q_ASSERT(!uuid.isNull());
Q_ASSERT(m_customIcons.contains(uuid));
m_customIcons.remove(uuid);
@@ -225,9 +235,9 @@ void Metadata::setRecycleBinEnabled(bool value)
m_recycleBinEnabled = value;
}
-void Metadata::setRecycleBinUuid(const Uuid& value)
+void Metadata::setRecycleBin(Group* group)
{
- m_recycleBinUuid = value;
+ m_recycleBin = group;
}
void Metadata::setRecycleBinChanged(const QDateTime& value)
@@ -235,9 +245,9 @@ void Metadata::setRecycleBinChanged(const QDateTime& value)
m_recycleBinChanged = value;
}
-void Metadata::setEntryTemplatesGroup(const Uuid& value)
+void Metadata::setEntryTemplatesGroup(Group* group)
{
- m_entryTemplatesGroup = value;
+ m_entryTemplatesGroup = group;
}
void Metadata::setEntryTemplatesGroupChanged(const QDateTime& value)
@@ -245,14 +255,14 @@ void Metadata::setEntryTemplatesGroupChanged(const QDateTime& value)
m_entryTemplatesGroupChanged = value;
}
-void Metadata::setLastSelectedGroup(const Uuid& value)
+void Metadata::setLastSelectedGroup(Group* group)
{
- m_lastSelectedGroup = value;
+ m_lastSelectedGroup = group;
}
-void Metadata::setLastTopVisibleGroup(const Uuid& value)
+void Metadata::setLastTopVisibleGroup(Group* group)
{
- m_lastTopVisibleGroup = value;
+ m_lastTopVisibleGroup = group;
}
void Metadata::addCustomField(const QString& key, const QString& value)
diff --git a/src/core/Metadata.h b/src/core/Metadata.h
index 175f5c958..7fca99c5d 100644
--- a/src/core/Metadata.h
+++ b/src/core/Metadata.h
@@ -24,10 +24,15 @@
#include
#include
-class Metadata
+class Database;
+class Group;
+
+class Metadata : public QObject
{
+ Q_OBJECT
+
public:
- Metadata();
+ Metadata(Database* parent);
QString generator() const;
QString name() const;
@@ -45,12 +50,12 @@ public:
bool autoEnableVisualHiding() const;
QHash customIcons() const;
bool recycleBinEnabled() const;
- Uuid recycleBinUuid() const;
+ const Group* recycleBin() const;
QDateTime recycleBinChanged() const;
- Uuid entryTemplatesGroup() const;
+ const Group* entryTemplatesGroup() const;
QDateTime entryTemplatesGroupChanged() const;
- Uuid lastSelectedGroup() const;
- Uuid lastTopVisibleGroup() const;
+ const Group* lastSelectedGroup() const;
+ const Group* lastTopVisibleGroup() const;
QHash customFields() const;
void setGenerator(const QString& value);
@@ -70,12 +75,12 @@ public:
void addCustomIcon(const Uuid& uuid, const QImage& image);
void removeCustomIcon(const Uuid& uuid);
void setRecycleBinEnabled(bool value);
- void setRecycleBinUuid(const Uuid& value);
+ void setRecycleBin(Group* group);
void setRecycleBinChanged(const QDateTime& value);
- void setEntryTemplatesGroup(const Uuid& value);
+ void setEntryTemplatesGroup(Group* group);
void setEntryTemplatesGroupChanged(const QDateTime& value);
- void setLastSelectedGroup(const Uuid& value);
- void setLastTopVisibleGroup(const Uuid& value);
+ void setLastSelectedGroup(Group* group);
+ void setLastTopVisibleGroup(Group* group);
void addCustomField(const QString& key, const QString& value);
void removeCustomField(const QString& key);
@@ -99,12 +104,12 @@ private:
QHash m_customIcons;
bool m_recycleBinEnabled;
- Uuid m_recycleBinUuid;
+ Group* m_recycleBin;
QDateTime m_recycleBinChanged;
- Uuid m_entryTemplatesGroup;
+ Group* m_entryTemplatesGroup;
QDateTime m_entryTemplatesGroupChanged;
- Uuid m_lastSelectedGroup;
- Uuid m_lastTopVisibleGroup;
+ Group* m_lastSelectedGroup;
+ Group* m_lastTopVisibleGroup;
QHash m_customFields;
};
diff --git a/src/core/Parser.cpp b/src/core/Parser.cpp
index 0b0d13653..2b9cbc03d 100644
--- a/src/core/Parser.cpp
+++ b/src/core/Parser.cpp
@@ -17,6 +17,7 @@
#include "Parser.h"
+#include
#include
#include "Database.h"
@@ -35,24 +36,31 @@ bool Parser::parse(const QString& filename)
m_xml.setDevice(&file);
m_tmpParent = new Group();
+ m_tmpParent->setParent(m_db);
if (!m_xml.error() && m_xml.readNextStartElement()) {
if (m_xml.name() == "KeePassFile") {
parseKeePassFile();
}
- else {
- raiseError();
- }
}
- if (!m_tmpParent->children().isEmpty()) {
- delete m_tmpParent;
+ if (!m_xml.error() && !m_tmpParent->children().isEmpty()) {
raiseError();
}
+ delete m_tmpParent;
+
return !m_xml.error();
}
+QString Parser::errorMsg()
+{
+ return QString("%1\nLine %2, column %3")
+ .arg(m_xml.errorString())
+ .arg(m_xml.lineNumber())
+ .arg(m_xml.columnNumber());
+}
+
void Parser::parseKeePassFile()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile");
@@ -65,7 +73,7 @@ void Parser::parseKeePassFile()
parseRoot();
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -109,28 +117,28 @@ void Parser::parseMeta()
m_meta->setRecycleBinEnabled(readBool());
}
else if (m_xml.name() == "RecycleBinUUID") {
- m_meta->setRecycleBinUuid(readUuid());
+ m_meta->setRecycleBin(getGroup(readUuid()));
}
else if (m_xml.name() == "RecycleBinChanged") {
m_meta->setRecycleBinChanged(readDateTime());
}
else if (m_xml.name() == "EntryTemplatesGroup") {
- m_meta->setEntryTemplatesGroup(readUuid());
+ m_meta->setEntryTemplatesGroup(getGroup(readUuid()));
}
else if (m_xml.name() == "EntryTemplatesGroupChanged") {
m_meta->setEntryTemplatesGroupChanged(readDateTime());
}
else if (m_xml.name() == "LastSelectedGroup") {
- m_meta->setLastSelectedGroup(readUuid());
+ m_meta->setLastSelectedGroup(getGroup(readUuid()));
}
else if (m_xml.name() == "LastTopVisibleGroup") {
- m_meta->setLastTopVisibleGroup(readUuid());
+ m_meta->setLastTopVisibleGroup(getGroup(readUuid()));
}
else if (m_xml.name() == "CustomData") {
parseCustomData();
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -159,7 +167,7 @@ void Parser::parseMemoryProtection()
m_meta->setAutoEnableVisualHiding(readBool());
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -173,7 +181,7 @@ void Parser::parseCustomIcons()
parseIcon();
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -193,7 +201,7 @@ void Parser::parseIcon()
m_meta->addCustomIcon(uuid, image);
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -202,7 +210,10 @@ void Parser::parseCustomData()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
- // TODO
+ // TODO implement
+ while (!m_xml.error() && m_xml.readNextStartElement()) {
+ skipCurrentElement();
+ }
}
void Parser::parseRoot()
@@ -218,9 +229,10 @@ void Parser::parseRoot()
}
else if (m_xml.name() == "DeletedObjects") {
// TODO implement
+ skipCurrentElement();
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -237,7 +249,7 @@ Group* Parser::parseGroup()
raiseError();
}
else {
- group = getGroup(uuid);
+ group = getGroup(uuid);
}
}
else if (m_xml.name() == "Name") {
@@ -268,16 +280,14 @@ Group* Parser::parseGroup()
}
else if (m_xml.name() == "EnableAutoType") {
// TODO implement
+ skipCurrentElement();
}
else if (m_xml.name() == "EnableSearching") {
// TODO implement
+ skipCurrentElement();
}
else if (m_xml.name() == "LastTopVisibleEntry") {
- Uuid uuid = readUuid();
- if (uuid.isNull())
- group->setLastTopVisibleEntry(0);
- else
- group->setLastTopVisibleEntry(getEntry(uuid));
+ group->setLastTopVisibleEntry(getEntry(readUuid()));
}
else if (m_xml.name() == "Group") {
Group* newGroup = parseGroup();
@@ -292,7 +302,7 @@ Group* Parser::parseGroup()
}
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
@@ -349,9 +359,11 @@ Entry* Parser::parseEntry()
parseEntryHistory();
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
+
+ return entry;
}
void Parser::parseEntryString(Entry *entry)
@@ -367,7 +379,7 @@ void Parser::parseEntryString(Entry *entry)
entry->addAttribute(key, readString());
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -385,7 +397,7 @@ void Parser::parseEntryBinary(Entry *entry)
entry->addAttachment(key, readBinary());
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -408,7 +420,7 @@ void Parser::parseAutoType(Entry* entry)
parseAutoTypeAssoc(entry);
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -427,7 +439,7 @@ void Parser::parseAutoTypeAssoc(Entry *entry)
entry->addAutoTypeAssociation(assoc);
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -439,9 +451,10 @@ void Parser::parseEntryHistory()
while (!m_xml.error() && m_xml.readNextStartElement()) {
if (m_xml.name() == "Entry") {
// TODO implement
+ skipCurrentElement();
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
}
@@ -474,7 +487,7 @@ TimeInfo Parser::parseTimes()
timeInfo.setLocationChanged(readDateTime());
}
else {
- m_xml.skipCurrentElement();
+ skipCurrentElement();
}
}
@@ -490,14 +503,15 @@ bool Parser::readBool()
{
QString str = readString();
- if (str == "True") {
+ if (str.compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) {
return true;
}
- else if (str == "False") {
+ else if (str.compare(QLatin1String("False"), Qt::CaseInsensitive) == 0) {
return false;
}
else {
raiseError();
+ return false;
}
}
@@ -516,6 +530,11 @@ QDateTime Parser::readDateTime()
QColor Parser::readColor()
{
QString colorStr = readString();
+
+ if (colorStr.isEmpty()) {
+ return QColor();
+ }
+
if (colorStr.length() != 7 || colorStr[0] != '#') {
raiseError();
return QColor();
@@ -525,7 +544,7 @@ QColor Parser::readColor()
for (int i=0; i<= 2; i++) {
QString rgbPartStr = colorStr.mid(1 + 2*i, 2);
bool ok;
- int rgbPart = rgbPartStr.toInt(&ok);
+ int rgbPart = rgbPartStr.toInt(&ok, 16);
if (!ok || rgbPart > 255) {
raiseError();
return QColor();
@@ -563,7 +582,7 @@ Uuid Parser::readUuid()
return Uuid();
}
else {
- return Uuid(readBinary());
+ return Uuid(uuidBin);
}
}
@@ -574,6 +593,10 @@ QByteArray Parser::readBinary()
Group* Parser::getGroup(const Uuid& uuid)
{
+ if (uuid.isNull()) {
+ return 0;
+ }
+
Q_FOREACH (Group* group, m_groups) {
if (group->uuid() == uuid) {
return group;
@@ -589,6 +612,10 @@ Group* Parser::getGroup(const Uuid& uuid)
Entry* Parser::getEntry(const Uuid& uuid)
{
+ if (uuid.isNull()) {
+ return 0;
+ }
+
Q_FOREACH (Entry* entry, m_entries) {
if (entry->uuid() == uuid) {
return entry;
@@ -606,3 +633,9 @@ void Parser::raiseError()
{
m_xml.raiseError(tr("Invalid database file"));
}
+
+void Parser::skipCurrentElement()
+{
+ qDebug() << "Parser::skipCurrentElement(): skip: " << m_xml.name();
+ m_xml.skipCurrentElement();
+}
diff --git a/src/core/Parser.h b/src/core/Parser.h
index ff2e2f8ef..4d18203cf 100644
--- a/src/core/Parser.h
+++ b/src/core/Parser.h
@@ -37,6 +37,7 @@ class Parser : public QObject
public:
Parser(Database* db);
bool parse(const QString& filename);
+ QString errorMsg();
private:
void parseKeePassFile();
@@ -66,6 +67,7 @@ private:
Group* getGroup(const Uuid& uuid);
Entry* getEntry(const Uuid& uuid);
void raiseError();
+ void skipCurrentElement();
QXmlStreamReader m_xml;
Database* m_db;
diff --git a/src/core/Uuid.cpp b/src/core/Uuid.cpp
index 8757ab2b2..4afa7ffb9 100644
--- a/src/core/Uuid.cpp
+++ b/src/core/Uuid.cpp
@@ -39,10 +39,9 @@ Uuid::Uuid(const QByteArray& data)
m_data = data;
}
-
-QString Uuid::toString() const
+QString Uuid::toBase64() const
{
- return m_data.toHex();
+ return m_data.toBase64();
}
QByteArray Uuid::toByteArray() const
diff --git a/src/core/Uuid.h b/src/core/Uuid.h
index d177e0687..74b5cf4c6 100644
--- a/src/core/Uuid.h
+++ b/src/core/Uuid.h
@@ -27,7 +27,7 @@ public:
Uuid();
Uuid(bool generate);
Uuid(const QByteArray& data);
- QString toString() const;
+ QString toBase64() const;
QByteArray toByteArray() const;
bool isNull() const;
bool operator==(const Uuid& other) const;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 000000000..df232213d
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,70 @@
+# Copyright (C) 2010 Felix Geyer
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or (at your option)
+# version 3 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src)
+
+set( KEEPASSX_TEST_TREE ${CMAKE_SOURCE_DIR}/tests )
+configure_file( config-keepassx-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx-tests.h )
+
+macro (ADD_UNIT_TEST _test_NAME)
+ set(_srcList ${ARGN})
+ set(_targetName ${_test_NAME})
+ if( ${ARGV1} STREQUAL "TESTNAME" )
+ set(_targetName ${ARGV2})
+ list(REMOVE_AT _srcList 0 1)
+ endif( ${ARGV1} STREQUAL "TESTNAME" )
+
+ set(_nogui)
+ list(GET ${_srcList} 0 first_PARAM)
+ if( ${first_PARAM} STREQUAL "NOGUI" )
+ set(_nogui "NOGUI")
+ endif( ${first_PARAM} STREQUAL "NOGUI" )
+
+ automoc4_add_executable( ${_test_NAME} ${_srcList} )
+
+ if(NOT TEST_OUTPUT)
+ set(TEST_OUTPUT plaintext)
+ endif(NOT TEST_OUTPUT)
+ set(TEST_OUTPUT ${TEST_OUTPUT} CACHE STRING "The output to generate when running the QTest unit tests")
+
+ get_target_property( loc ${_test_NAME} LOCATION )
+
+ if (KDE4_TEST_OUTPUT STREQUAL "xml")
+ add_test( ${_targetName} ${loc} -xml -o ${_targetName}.tml)
+ else (KDE4_TEST_OUTPUT STREQUAL "xml")
+ add_test( ${_targetName} ${loc} )
+ endif (KDE4_TEST_OUTPUT STREQUAL "xml")
+
+ if (NOT MSVC_IDE) #not needed for the ide
+ # if the tests are EXCLUDE_FROM_ALL, add a target "buildtests" to build all tests
+ if (NOT WITH_TESTS)
+ get_directory_property(_buildtestsAdded BUILDTESTS_ADDED)
+ if(NOT _buildtestsAdded)
+ add_custom_target(buildtests)
+ set_directory_properties(PROPERTIES BUILDTESTS_ADDED TRUE)
+ endif(NOT _buildtestsAdded)
+ add_dependencies(buildtests ${_test_NAME})
+ endif (NOT WITH_TESTS)
+ endif (NOT MSVC_IDE)
+
+endmacro (ADD_UNIT_TEST)
+
+
+add_unit_test( testparser TestParser.cpp )
+target_link_libraries( testparser keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} )
+
+add_unit_test( testgroup TestGroup.cpp )
+target_link_libraries( testgroup keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} )
diff --git a/tests/NewDatabase.xml b/tests/NewDatabase.xml
new file mode 100644
index 000000000..52c1b91af
--- /dev/null
+++ b/tests/NewDatabase.xml
@@ -0,0 +1,287 @@
+
+
+
+ KeePass
+ ANAME
+ 2010-08-08T17:24:53Z
+ ADESC
+ 2010-08-08T17:27:12Z
+ DEFUSERNAME
+ 2010-08-08T17:27:45Z
+ 127
+
+ False
+ True
+ False
+ True
+ False
+ True
+
+ True
+ 7PAwxNhPaE2klutz45i2xg==
+ 2010-08-08T17:24:17Z
+ AAAAAAAAAAAAAAAAAAAAAA==
+ 2010-08-08T17:24:19Z
+ zKuE27EWr0mlU75b2SRkTQ==
+ zKuE27EWr0mlU75b2SRkTQ==
+
+
+
+
+ zKuE27EWr0mlU75b2SRkTQ==
+ NewDatabase
+
+ 49
+
+ 2010-08-08T17:24:27Z
+ 2010-08-08T17:24:27Z
+ 2010-08-09T09:09:44Z
+ 2010-08-08T17:24:17Z
+ False
+ 2
+ 2010-08-08T17:24:27Z
+
+ True
+
+ null
+ null
+ QW4G0r/z90qql4iKZ0RwlA==
+
+ QW4G0r/z90qql4iKZ0RwlA==
+ 0
+ #0000FF
+ #000000
+ OMGAURL
+
+ 2010-08-09T09:09:31Z
+ 2010-08-08T17:24:53Z
+ 2010-08-09T09:09:31Z
+ 2011-08-08T17:25:58Z
+ True
+ 6
+ 2010-08-08T17:24:53Z
+
+
+ customfield
+ customfield value
+
+
+ Notes
+ Notes
+
+
+ Password
+ Password
+
+
+ Title
+ Sample Entry
+
+
+ URL
+ bleh
+
+
+ UserName
+ User Name
+
+
+ True
+ 1
+ {USERNAME}{TAB}{PASSWORD}{ENTER}
+
+ Target Window
+ {USERNAME}{TAB}{PASSWORD}{TAB}{ENTER}
+
+
+ Edit Entry
+ {Title}{UserName}{UserName}
+
+
+
+
+
+ abLbFtNUfEi5TmbaxiW6yg==
+ General
+
+ 48
+
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:17Z
+ False
+ 0
+ 2010-08-08T17:24:53Z
+
+ True
+
+ null
+ null
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+
+ u1lTRAICOkWv5QSl2xyU8w==
+ Windows
+
+ 38
+
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:17Z
+ False
+ 0
+ 2010-08-08T17:24:53Z
+
+ True
+
+ null
+ null
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+
+ bFe1/LfewEuvlTsT8nJRRg==
+ Network
+
+ 3
+
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:17Z
+ False
+ 0
+ 2010-08-08T17:24:53Z
+
+ True
+
+ null
+ null
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+
+ wHmj/+6vTkOpG/eeVp3yjg==
+ Internet
+
+ 1
+
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:17Z
+ False
+ 0
+ 2010-08-08T17:24:53Z
+
+ True
+
+ null
+ null
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+
+ 9QLqqFgc5EC7ptm2TI1hDA==
+ eMail
+
+ 19
+
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:17Z
+ False
+ 0
+ 2010-08-08T17:24:53Z
+
+ True
+
+ null
+ null
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+
+ wQru0ArOaEOy0uUio3subA==
+ Homebanking
+
+ 37
+
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:53Z
+ 2010-08-08T17:24:17Z
+ False
+ 0
+ 2010-08-08T17:24:53Z
+
+ True
+
+ null
+ null
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+
+ 7PAwxNhPaE2klutz45i2xg==
+ Recycle Bin
+
+ 43
+
+ 2010-08-09T09:09:44Z
+ 2010-08-09T09:09:44Z
+ 2010-08-09T09:09:44Z
+ 2010-08-09T09:07:16Z
+ False
+ 1
+ 2010-08-09T09:09:44Z
+
+ True
+
+ false
+ false
+ AAAAAAAAAAAAAAAAAAAAAA==
+
+ KIeQe6yDN0SbjIJ83NB++Q==
+ 0
+
+
+
+
+ 2010-08-09T09:09:40Z
+ 2010-08-09T09:09:37Z
+ 2010-08-09T09:09:44Z
+ 2010-08-09T09:07:16Z
+ False
+ 2
+ 2010-08-09T09:09:44Z
+
+
+ Notes
+
+
+
+ Password
+ 5Ciyy3kcVSPFUFqTuK1o
+
+
+ Title
+ test
+
+
+ URL
+
+
+
+ UserName
+ DEFUSERNAME
+
+
+ True
+ 0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp
new file mode 100644
index 000000000..585bac825
--- /dev/null
+++ b/tests/TestGroup.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 Felix Geyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include "core/Database.h"
+
+class TestGroup : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testParenting();
+};
+
+void TestGroup::testParenting()
+{
+ Database* db = new Database();
+ Group* tmpRoot = new Group();
+ tmpRoot->setParent(db);
+
+ Group* g1 = new Group();
+ Group* g2 = new Group();
+ Group* g3 = new Group();
+ Group* g4 = new Group();
+ g1->setParent(tmpRoot);
+ g2->setParent(tmpRoot);
+ g3->setParent(tmpRoot);
+ g4->setParent(tmpRoot);
+
+ g2->setParent(g1);
+ g4->setParent(g3);
+ g3->setParent(g1);
+ g1->setParent(db);
+
+ QVERIFY(g1->parent() == db);
+ QVERIFY(g2->parent() == g1);
+ QVERIFY(g3->parent() == g1);
+ QVERIFY(g4->parent() == g3);
+
+ QVERIFY(tmpRoot->children().size() == 0);
+ QVERIFY(g1->children().size() == 2);
+ QVERIFY(g2->children().size() == 0);
+ QVERIFY(g3->children().size() == 1);
+ QVERIFY(g4->children().size() == 0);
+
+ QVERIFY(g1->children().contains(g2));
+ QVERIFY(g1->children().contains(g3));
+ QVERIFY(g3->children().contains(g4));
+}
+
+QTEST_MAIN(TestGroup);
+
+#include "TestGroup.moc"
diff --git a/tests/TestParser.cpp b/tests/TestParser.cpp
new file mode 100644
index 000000000..b263fa2f4
--- /dev/null
+++ b/tests/TestParser.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 Felix Geyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include "core/Database.h"
+#include "core/Metadata.h"
+#include "core/Parser.h"
+#include "config-keepassx-tests.h"
+
+class TestParser : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase();
+ void testMetadata();
+ void testGroups();
+
+private:
+ QDateTime genDT(int year, int month, int day, int hour, int min, int second);
+
+ Database* m_db;
+};
+
+QDateTime TestParser::genDT(int year, int month, int day, int hour, int min, int second)
+{
+ QDate date(year, month, day);
+ QTime time(hour, min, second);
+ return QDateTime(date, time, Qt::UTC);
+}
+
+void TestParser::initTestCase()
+{
+ m_db = new Database();
+ Parser* parser = new Parser(m_db);
+ QString xmlFile = QString(KEEPASSX_TEST_DIR).append("/NewDatabase.xml");
+ QVERIFY(parser->parse(xmlFile));
+}
+
+void TestParser::testMetadata()
+{
+ QCOMPARE(m_db->metadata()->generator(), QLatin1String("KeePass"));
+ QCOMPARE(m_db->metadata()->name(), QLatin1String("ANAME"));
+ QCOMPARE(m_db->metadata()->nameChanged(), genDT(2010, 8, 8, 17, 24, 53));
+ QCOMPARE(m_db->metadata()->description(), QLatin1String("ADESC"));
+ QCOMPARE(m_db->metadata()->descriptionChanged(), genDT(2010, 8, 8, 17, 27, 12));
+ QCOMPARE(m_db->metadata()->defaultUserName(), QLatin1String("DEFUSERNAME"));
+ QCOMPARE(m_db->metadata()->defaultUserNameChanged(), genDT(2010, 8, 8, 17, 27, 45));
+ QCOMPARE(m_db->metadata()->maintenanceHistoryDays(), 127);
+ QCOMPARE(m_db->metadata()->protectTitle(), false);
+ QCOMPARE(m_db->metadata()->protectUsername(), true);
+ QCOMPARE(m_db->metadata()->protectPassword(), false);
+ QCOMPARE(m_db->metadata()->protectUrl(), true);
+ QCOMPARE(m_db->metadata()->protectNotes(), false);
+ QCOMPARE(m_db->metadata()->autoEnableVisualHiding(), true);
+ QCOMPARE(m_db->metadata()->recycleBinEnabled(), true);
+ QVERIFY(m_db->metadata()->recycleBin() != 0);
+ QCOMPARE(m_db->metadata()->recycleBin()->name(), QLatin1String("Recycle Bin"));
+ QCOMPARE(m_db->metadata()->recycleBinChanged(), genDT(2010, 8, 8, 17, 24, 17));
+ QVERIFY(m_db->metadata()->entryTemplatesGroup() == 0);
+ QCOMPARE(m_db->metadata()->entryTemplatesGroupChanged(), genDT(2010, 8, 8, 17, 24, 19));
+ QVERIFY(m_db->metadata()->lastSelectedGroup() != 0);
+ QCOMPARE(m_db->metadata()->lastSelectedGroup()->name(), QLatin1String("NewDatabase"));
+ QVERIFY(m_db->metadata()->lastTopVisibleGroup() != 0);
+ QCOMPARE(m_db->metadata()->lastTopVisibleGroup()->name(), QLatin1String("NewDatabase"));
+}
+
+void TestParser::testGroups()
+{
+ QVERIFY(m_db->rootGroup()->name() == QLatin1String("NewDatabase"));
+ QVERIFY(m_db->rootGroup()->uuid().toBase64() == QLatin1String("zKuE27EWr0mlU75b2SRkTQ=="));
+ QVERIFY(m_db->rootGroup()->isExpanded() == true);
+}
+
+QTEST_MAIN(TestParser);
+
+#include "TestParser.moc"
diff --git a/tests/config-keepassx-tests.h.cmake b/tests/config-keepassx-tests.h.cmake
new file mode 100644
index 000000000..dcbe20b0d
--- /dev/null
+++ b/tests/config-keepassx-tests.h.cmake
@@ -0,0 +1,3 @@
+/* config-keepassx-tests.h. Generated by cmake from config-keepassx-tests.h.cmake */
+
+#define KEEPASSX_TEST_DIR "${KEEPASSX_TEST_TREE}"