mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Feature : clip command (#578)
This commit is contained in:
parent
6c050c55d9
commit
a2e82dc883
@ -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
73
src/cli/Clip.cpp
Normal 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
27
src/cli/Clip.h
Normal 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
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ private slots:
|
||||
void testMergeConflict();
|
||||
void testMergeDatabase();
|
||||
void testMergeConflictKeepBoth();
|
||||
void testFindEntry();
|
||||
|
||||
private:
|
||||
Database* createMergeTestDatabase();
|
||||
|
Loading…
Reference in New Issue
Block a user