diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index e090ad1d8..86edb9c01 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -14,6 +14,8 @@
# along with this program. If not, see .
set(cli_SOURCES
+ Clip.cpp
+ Clip.h
EntropyMeter.cpp
EntropyMeter.h
Extract.cpp
diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp
new file mode 100644
index 000000000..b3d4fd107
--- /dev/null
+++ b/src/cli/Clip.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 KeePassXC Team
+ *
+ * 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
+
+#include "Clip.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "core/Database.h"
+#include "core/Entry.h"
+#include "core/Group.h"
+#include "gui/Clipboard.h"
+#include "keys/CompositeKey.h"
+
+int Clip::execute(int argc, char** argv)
+{
+ QApplication app(argc, argv);
+ QTextStream out(stdout);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QCoreApplication::translate("main", "Copy a password to the clipboard"));
+ parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database."));
+ parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to clip."));
+ parser.process(app);
+
+ const QStringList args = parser.positionalArguments();
+ if (args.size() != 2) {
+ parser.showHelp();
+ return EXIT_FAILURE;
+ }
+
+ out << "Insert the database password\n> ";
+ out.flush();
+
+ static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
+ QString line = inputTextStream.readLine();
+ CompositeKey key = CompositeKey::readFromLine(line);
+
+ Database* db = Database::openDatabaseFile(args.at(0), key);
+ if (!db) {
+ return EXIT_FAILURE;
+ }
+
+ QString entryId = args.at(1);
+ Entry* entry = db->rootGroup()->findEntry(entryId);
+ if (!entry) {
+ qCritical("Entry %s not found.", qPrintable(entryId));
+ return EXIT_FAILURE;
+ }
+
+ Clipboard::instance()->setText(entry->password());
+ return EXIT_SUCCESS;
+}
diff --git a/src/cli/Clip.h b/src/cli/Clip.h
new file mode 100644
index 000000000..944184095
--- /dev/null
+++ b/src/cli/Clip.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 KeePassXC Team
+ *
+ * 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 .
+ */
+
+#ifndef KEEPASSXC_CLIP_H
+#define KEEPASSXC_CLIP_H
+
+class Clip
+{
+public:
+ static int execute(int argc, char** argv);
+};
+
+#endif // KEEPASSXC_CLIP_H
diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp
index 81a9ddf07..74fa33da7 100644
--- a/src/cli/Extract.cpp
+++ b/src/cli/Extract.cpp
@@ -30,14 +30,14 @@
#include "format/KeePass2Reader.h"
#include "keys/CompositeKey.h"
-int Extract::execute(int argc, char **argv)
+int Extract::execute(int argc, char** argv)
{
QCoreApplication app(argc, argv);
QTextStream out(stdout);
QCommandLineParser parser;
- parser.setApplicationDescription(QCoreApplication::translate("main",
- "Extract and print the content of a database."));
+ parser.setApplicationDescription(
+ QCoreApplication::translate("main", "Extract and print the content of a database."));
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database to extract."));
parser.process(app);
@@ -75,8 +75,7 @@ int Extract::execute(int argc, char **argv)
if (reader.hasError()) {
if (xmlData.isEmpty()) {
qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString()));
- }
- else {
+ } else {
qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString()));
}
return EXIT_FAILURE;
diff --git a/src/cli/List.cpp b/src/cli/List.cpp
index 1e3488106..af3bf0c6d 100644
--- a/src/cli/List.cpp
+++ b/src/cli/List.cpp
@@ -30,7 +30,8 @@
#include "core/Group.h"
#include "keys/CompositeKey.h"
-void printGroup(Group* group, QString baseName, int depth) {
+void printGroup(Group* group, QString baseName, int depth)
+{
QTextStream out(stdout);
@@ -46,23 +47,21 @@ void printGroup(Group* group, QString baseName, int depth) {
}
for (Entry* entry : group->entries()) {
- out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n";
+ out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n";
}
for (Group* innerGroup : group->children()) {
printGroup(innerGroup, groupName, depth + 1);
}
-
}
-int List::execute(int argc, char **argv)
+int List::execute(int argc, char** argv)
{
QCoreApplication app(argc, argv);
QTextStream out(stdout);
QCommandLineParser parser;
- parser.setApplicationDescription(QCoreApplication::translate("main",
- "List database entries."));
+ parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries."));
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database."));
parser.process(app);
diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp
index aa399dd5b..8ff8a7b20 100644
--- a/src/cli/Merge.cpp
+++ b/src/cli/Merge.cpp
@@ -37,11 +37,15 @@ int Merge::execute(int argc, char** argv)
QCommandLineParser parser;
parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases."));
- parser.addPositionalArgument("database1", QCoreApplication::translate("main", "Path of the database to merge into."));
- parser.addPositionalArgument("database2", QCoreApplication::translate("main", "Path of the database to merge from."));
+ parser.addPositionalArgument("database1",
+ QCoreApplication::translate("main", "Path of the database to merge into."));
+ parser.addPositionalArgument("database2",
+ QCoreApplication::translate("main", "Path of the database to merge from."));
- QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password",
- QCoreApplication::translate("main", "Use the same password for both database files."));
+ QCommandLineOption samePasswordOption(
+ QStringList() << "s"
+ << "same-password",
+ QCoreApplication::translate("main", "Use the same password for both database files."));
parser.addOption(samePasswordOption);
parser.process(app);
@@ -54,22 +58,20 @@ int Merge::execute(int argc, char** argv)
out << "Insert the first database password\n> ";
out.flush();
-
+
static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
QString line1 = inputTextStream.readLine();
CompositeKey key1 = CompositeKey::readFromLine(line1);
CompositeKey key2;
if (parser.isSet("same-password")) {
- key2 = *key1.clone();
+ key2 = *key1.clone();
+ } else {
+ out << "Insert the second database password\n> ";
+ out.flush();
+ QString line2 = inputTextStream.readLine();
+ key2 = CompositeKey::readFromLine(line2);
}
- else {
- out << "Insert the second database password\n> ";
- out.flush();
- QString line2 = inputTextStream.readLine();
- key2 = CompositeKey::readFromLine(line2);
- }
-
Database* db1 = Database::openDatabaseFile(args.at(0), key1);
if (db1 == nullptr) {
@@ -104,5 +106,4 @@ int Merge::execute(int argc, char** argv)
out << "Successfully merged the database files.\n";
return EXIT_SUCCESS;
-
}
diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp
index 9222a093d..59c0219a2 100644
--- a/src/cli/Show.cpp
+++ b/src/cli/Show.cpp
@@ -30,16 +30,15 @@
#include "core/Group.h"
#include "keys/CompositeKey.h"
-int Show::execute(int argc, char **argv)
+int Show::execute(int argc, char** argv)
{
QCoreApplication app(argc, argv);
QTextStream out(stdout);
QCommandLineParser parser;
- parser.setApplicationDescription(QCoreApplication::translate("main",
- "Show a password."));
+ parser.setApplicationDescription(QCoreApplication::translate("main", "Show a password."));
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database."));
- parser.addPositionalArgument("uuid", QCoreApplication::translate("main", "Uuid of the entry to show"));
+ parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to show."));
parser.process(app);
const QStringList args = parser.positionalArguments();
@@ -60,10 +59,10 @@ int Show::execute(int argc, char **argv)
return EXIT_FAILURE;
}
- Uuid uuid = Uuid::fromHex(args.at(1));
- Entry* entry = db->resolveEntry(uuid);
- if (entry == nullptr) {
- qCritical("No entry found with uuid %s", qPrintable(uuid.toHex()));
+ QString entryId = args.at(1);
+ Entry* entry = db->rootGroup()->findEntry(entryId);
+ if (!entry) {
+ qCritical("Entry %s not found.", qPrintable(entryId));
return EXIT_FAILURE;
}
diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp
index b27b7483f..f6b5df8d7 100644
--- a/src/cli/keepassxc-cli.cpp
+++ b/src/cli/keepassxc-cli.cpp
@@ -21,6 +21,7 @@
#include
#include
+#include
#include
#include
#include
@@ -35,7 +36,7 @@
#include
#endif
-int main(int argc, char **argv)
+int main(int argc, char** argv)
{
#ifdef QT_NO_DEBUG
Tools::disableCoreDumps();
@@ -53,6 +54,7 @@ int main(int argc, char **argv)
QString description("KeePassXC command line interface.");
description = description.append(QString("\n\nAvailable commands:"));
+ description = description.append(QString("\n clip\t\tCopy a password to the clipboard."));
description = description.append(QString("\n extract\tExtract and print the content of a database."));
description = description.append(QString("\n entropy-meter\tCalculate password entropy."));
description = description.append(QString("\n list\t\tList database entries."));
@@ -82,7 +84,10 @@ int main(int argc, char **argv)
int exitCode = EXIT_FAILURE;
- if (commandName == "entropy-meter") {
+ if (commandName == "clip") {
+ argv[0] = const_cast("keepassxc-cli clip");
+ exitCode = Clip::execute(argc, argv);
+ } else if (commandName == "entropy-meter") {
argv[0] = const_cast("keepassxc-cli entropy-meter");
exitCode = EntropyMeter::execute(argc, argv);
} else if (commandName == "extract") {
@@ -110,5 +115,4 @@ int main(int argc, char **argv)
#endif
return exitCode;
-
}
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 5b5a707f9..b3897efae 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -367,7 +367,7 @@ void Database::startModifiedTimer()
m_timer->start(150);
}
-const CompositeKey & Database::key() const
+const CompositeKey& Database::key() const
{
return m_data.key;
}
diff --git a/src/core/Group.cpp b/src/core/Group.cpp
index bd4f8851b..886e55cae 100644
--- a/src/core/Group.cpp
+++ b/src/core/Group.cpp
@@ -483,7 +483,24 @@ QList Group::entriesRecursive(bool includeHistoryItems) const
return entryList;
}
-Entry* Group::findEntry(const Uuid& uuid)
+Entry* Group::findEntry(QString entryId)
+{
+ Q_ASSERT(!entryId.isEmpty());
+ Q_ASSERT(!entryId.isNull());
+
+ if (Uuid::isUuid(entryId)) {
+ Uuid entryUuid = Uuid::fromHex(entryId);
+ for (Entry* entry : entriesRecursive(false)) {
+ if (entry->uuid() == entryUuid) {
+ return entry;
+ }
+ }
+ }
+
+ return findEntryByPath(entryId);
+}
+
+Entry* Group::findEntryByUuid(const Uuid& uuid)
{
Q_ASSERT(!uuid.isNull());
for (Entry* entry : asConst(m_entries)) {
@@ -495,6 +512,29 @@ Entry* Group::findEntry(const Uuid& uuid)
return nullptr;
}
+Entry* Group::findEntryByPath(QString entryPath, QString basePath)
+{
+
+ Q_ASSERT(!entryPath.isEmpty());
+ Q_ASSERT(!entryPath.isNull());
+
+ for (Entry* entry : asConst(m_entries)) {
+ QString currentEntryPath = basePath + entry->title();
+ if (entryPath == currentEntryPath) {
+ return entry;
+ }
+ }
+
+ for (Group* group : asConst(m_children)) {
+ Entry* entry = group->findEntryByPath(entryPath, basePath + group->name() + QString("/"));
+ if (entry != nullptr) {
+ return entry;
+ }
+ }
+
+ return nullptr;
+}
+
QList Group::groupsRecursive(bool includeSelf) const
{
QList groupList;
@@ -551,10 +591,10 @@ void Group::merge(const Group* other)
const QList dbEntries = other->entries();
for (Entry* entry : dbEntries) {
// entries are searched by uuid
- if (!findEntry(entry->uuid())) {
+ if (!findEntryByUuid(entry->uuid())) {
entry->clone(Entry::CloneNoFlags)->setGroup(this);
} else {
- resolveConflict(findEntry(entry->uuid()), entry);
+ resolveConflict(findEntryByUuid(entry->uuid()), entry);
}
}
diff --git a/src/core/Group.h b/src/core/Group.h
index e3e5e7554..a1b2bcb46 100644
--- a/src/core/Group.h
+++ b/src/core/Group.h
@@ -78,7 +78,9 @@ public:
static const int DefaultIconNumber;
static const int RecycleBinIconNumber;
- Entry* findEntry(const Uuid& uuid);
+ Entry* findEntry(QString entryId);
+ Entry* findEntryByUuid(const Uuid& uuid);
+ Entry* findEntryByPath(QString entryPath, QString basePath = QString(""));
Group* findChildByName(const QString& name);
void setUuid(const Uuid& uuid);
void setName(const QString& name);
diff --git a/src/core/Uuid.cpp b/src/core/Uuid.cpp
index 1b046c5a3..1b531159f 100644
--- a/src/core/Uuid.cpp
+++ b/src/core/Uuid.cpp
@@ -22,6 +22,8 @@
#include "crypto/Random.h"
const int Uuid::Length = 16;
+const QRegExp Uuid::HexRegExp = QRegExp(QString("^[0-9A-F]{%1}$").arg(QString::number(Uuid::Length * 2)),
+ Qt::CaseInsensitive);
Uuid::Uuid()
: m_data(Length, 0)
@@ -115,3 +117,8 @@ QDataStream& operator>>(QDataStream& stream, Uuid& uuid)
return stream;
}
+
+bool Uuid::isUuid(const QString& uuid)
+{
+ return Uuid::HexRegExp.exactMatch(uuid);
+}
diff --git a/src/core/Uuid.h b/src/core/Uuid.h
index 4164399d6..ecb20e0c3 100644
--- a/src/core/Uuid.h
+++ b/src/core/Uuid.h
@@ -20,6 +20,7 @@
#include
#include
+#include
class Uuid
{
@@ -36,8 +37,10 @@ public:
bool operator==(const Uuid& other) const;
bool operator!=(const Uuid& other) const;
static const int Length;
+ static const QRegExp HexRegExp;
static Uuid fromBase64(const QString& str);
static Uuid fromHex(const QString& str);
+ static bool isUuid(const QString& str);
private:
QByteArray m_data;
diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp
index e87e6cedc..425bc75c7 100644
--- a/tests/TestGroup.cpp
+++ b/tests/TestGroup.cpp
@@ -567,3 +567,55 @@ Database* TestGroup::createMergeTestDatabase()
return db;
}
+
+void TestGroup::testFindEntry()
+{
+ Database* db = new Database();
+
+ Entry* entry1 = new Entry();
+ entry1->setTitle(QString("entry1"));
+ entry1->setGroup(db->rootGroup());
+ entry1->setUuid(Uuid::random());
+
+ Group* group1 = new Group();
+ group1->setName("group1");
+
+ Entry* entry2 = new Entry();
+
+ entry2->setTitle(QString("entry2"));
+ entry2->setGroup(group1);
+ entry2->setUuid(Uuid::random());
+
+ group1->setParent(db->rootGroup());
+
+ Entry* entry;
+
+ entry = db->rootGroup()->findEntry(entry1->uuid().toHex());
+ QVERIFY(entry != nullptr);
+ QCOMPARE(entry->title(), QString("entry1"));
+
+ entry = db->rootGroup()->findEntry(QString("entry1"));
+ QVERIFY(entry != nullptr);
+ QCOMPARE(entry->title(), QString("entry1"));
+
+ entry = db->rootGroup()->findEntry(entry2->uuid().toHex());
+ QVERIFY(entry != nullptr);
+ QCOMPARE(entry->title(), QString("entry2"));
+
+ entry = db->rootGroup()->findEntry(QString("group1/entry2"));
+ QVERIFY(entry != nullptr);
+ QCOMPARE(entry->title(), QString("entry2"));
+
+ entry = db->rootGroup()->findEntry(QString("invalid/path/to/entry2"));
+ QVERIFY(entry == nullptr);
+
+ // A valid UUID that does not exist in this database.
+ entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc75281"));
+ QVERIFY(entry == nullptr);
+
+ // An invalid UUID.
+ entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc"));
+ QVERIFY(entry == nullptr);
+
+ delete db;
+}
diff --git a/tests/TestGroup.h b/tests/TestGroup.h
index c9ed8f087..87795dea2 100644
--- a/tests/TestGroup.h
+++ b/tests/TestGroup.h
@@ -38,6 +38,7 @@ private slots:
void testMergeConflict();
void testMergeDatabase();
void testMergeConflictKeepBoth();
+ void testFindEntry();
private:
Database* createMergeTestDatabase();