Feature : clip command (#578)

This commit is contained in:
louib 2017-05-19 14:04:11 -04:00 committed by GitHub
parent 6c050c55d9
commit a2e82dc883
15 changed files with 250 additions and 41 deletions

View File

@ -14,6 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(cli_SOURCES
Clip.cpp
Clip.h
EntropyMeter.cpp
EntropyMeter.h
Extract.cpp

73
src/cli/Clip.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include "Clip.h"
#include <QApplication>
#include <QClipboard>
#include <QCommandLineParser>
#include <QStringList>
#include <QTextStream>
#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;
}

27
src/cli/Clip.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_CLIP_H
#define KEEPASSXC_CLIP_H
class Clip
{
public:
static int execute(int argc, char** argv);
};
#endif // KEEPASSXC_CLIP_H

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -21,6 +21,7 @@
#include <QCoreApplication>
#include <QStringList>
#include <cli/Clip.h>
#include <cli/EntropyMeter.h>
#include <cli/Extract.h>
#include <cli/List.h>
@ -35,7 +36,7 @@
#include <sanitizer/lsan_interface.h>
#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<char*>("keepassxc-cli clip");
exitCode = Clip::execute(argc, argv);
} else if (commandName == "entropy-meter") {
argv[0] = const_cast<char*>("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;
}

View File

@ -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;
}

View File

@ -483,7 +483,24 @@ QList<Entry*> 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<const Group*> Group::groupsRecursive(bool includeSelf) const
{
QList<const Group*> groupList;
@ -551,10 +591,10 @@ void Group::merge(const Group* other)
const QList<Entry*> 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);
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -20,6 +20,7 @@
#include <QByteArray>
#include <QString>
#include <QRegExp>
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;

View File

@ -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;
}

View File

@ -38,6 +38,7 @@ private slots:
void testMergeConflict();
void testMergeDatabase();
void testMergeConflictKeepBoth();
void testFindEntry();
private:
Database* createMergeTestDatabase();