diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 18d737d2e..6ee280d40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,8 +66,8 @@ set(keepassx_SOURCES core/Tools.cpp core/Translator.cpp core/Uuid.cpp - cli/PasswordInput.cpp - cli/PasswordInput.h + cli/Utils.cpp + cli/Utils.h crypto/Crypto.cpp crypto/CryptoHash.cpp crypto/Random.cpp diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 7571bc194..ab12bb1bc 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -15,8 +15,10 @@ * along with this program. If not, see . */ +#include #include #include +#include #include "Clip.h" @@ -25,11 +27,11 @@ #include #include -#include "gui/UnlockDatabaseDialog.h" +#include "cli/Utils.h" #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" -#include "gui/Clipboard.h" +#include "gui/UnlockDatabaseDialog.h" Clip::Clip() { @@ -50,6 +52,7 @@ int Clip::execute(int argc, char** argv) } QTextStream out(stdout); + QApplication app(argc, argv); QCommandLineParser parser; parser.setApplicationDescription(this->description); @@ -59,17 +62,19 @@ int Clip::execute(int argc, char** argv) QObject::tr("Use a GUI prompt unlocking the database.")); parser.addOption(guiPrompt); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip.")); + parser.addPositionalArgument( + "timeout", + QObject::tr("Timeout in seconds before clearing the clipboard."), + QString("[timeout]")); parser.process(arguments); const QStringList args = parser.positionalArguments(); - if (args.size() != 2) { - QCoreApplication app(argc, argv); + if (args.size() != 2 && args.size() != 3) { out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli clip"); return EXIT_FAILURE; } Database* db = nullptr; - QApplication app(argc, argv); if (parser.isSet("gui-prompt")) { db = UnlockDatabaseDialog::openDatabasePrompt(args.at(0)); } else { @@ -79,12 +84,20 @@ int Clip::execute(int argc, char** argv) if (!db) { return EXIT_FAILURE; } - return this->clipEntry(db, args.at(1)); + return this->clipEntry(db, args.at(1), args.value(2)); } -int Clip::clipEntry(Database* database, QString entryPath) +int Clip::clipEntry(Database* database, QString entryPath, QString timeout) { + int timeoutSeconds = 0; + if (!timeout.isEmpty() && !timeout.toInt()) { + qCritical("Invalid timeout value %s.", qPrintable(timeout)); + return EXIT_FAILURE; + } else if (!timeout.isEmpty()) { + timeoutSeconds = timeout.toInt(); + } + QTextStream outputTextStream(stdout, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntry(entryPath); if (!entry) { @@ -92,7 +105,25 @@ int Clip::clipEntry(Database* database, QString entryPath) return EXIT_FAILURE; } - Clipboard::instance()->setText(entry->password()); - return EXIT_SUCCESS; + int exitCode = Utils::clipText(entry->password()); + if (exitCode != EXIT_SUCCESS) { + return exitCode; + } + outputTextStream << "Entry's password copied to the clipboard!" << endl; + + if (!timeoutSeconds) { + return exitCode; + } + + while (timeoutSeconds > 0) { + outputTextStream << "\rClearing the clipboard in " << timeoutSeconds << " seconds..."; + outputTextStream.flush(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + timeoutSeconds--; + } + Utils::clipText(""); + outputTextStream << "\nClipboard cleared!" << endl; + + return EXIT_SUCCESS; } diff --git a/src/cli/Clip.h b/src/cli/Clip.h index 195976355..1771e5581 100644 --- a/src/cli/Clip.h +++ b/src/cli/Clip.h @@ -26,7 +26,7 @@ public: Clip(); ~Clip(); int execute(int argc, char** argv); - int clipEntry(Database* database, QString entryPath); + int clipEntry(Database* database, QString entryPath, QString timeout); }; #endif // KEEPASSXC_CLIP_H diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 0073acba5..2095c177e 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -26,7 +26,7 @@ #include #include -#include "cli/PasswordInput.h" +#include "cli/Utils.h" #include "core/Database.h" #include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" @@ -66,7 +66,7 @@ int Extract::execute(int argc, char** argv) out << "Insert the database password\n> "; out.flush(); - QString line = PasswordInput::getPassword(); + QString line = Utils::getPassword(); CompositeKey key = CompositeKey::readFromLine(line); QString databaseFilename = args.at(0); diff --git a/src/cli/PasswordInput.cpp b/src/cli/Utils.cpp similarity index 56% rename from src/cli/PasswordInput.cpp rename to src/cli/Utils.cpp index 16913e956..b9866a1a2 100644 --- a/src/cli/PasswordInput.cpp +++ b/src/cli/Utils.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "PasswordInput.h" +#include "Utils.h" #ifdef Q_OS_WIN #include @@ -24,14 +24,11 @@ #include #endif +#include #include -PasswordInput::PasswordInput() -{ -} - -void PasswordInput::setStdinEcho(bool enable = true) +void Utils::setStdinEcho(bool enable = true) { #ifdef Q_OS_WIN HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); @@ -60,7 +57,7 @@ void PasswordInput::setStdinEcho(bool enable = true) #endif } -QString PasswordInput::getPassword() +QString Utils::getPassword() { static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); static QTextStream outputTextStream(stdout, QIODevice::WriteOnly); @@ -75,3 +72,52 @@ QString PasswordInput::getPassword() return line; } + +/* + * A valid and running event loop is needed to use the global QClipboard, + * so we need to use this from the CLI. + */ +int Utils::clipText(QString text) +{ + + QString programName = ""; + QStringList arguments; + +#ifdef Q_OS_UNIX + programName = "xclip"; + arguments << "-i" + << "-selection" + << "clipboard"; +#endif + +#ifdef Q_OS_MACOS + programName = "pbcopy"; +#endif + +#ifdef Q_OS_WIN + programName = "clip"; +#endif + + if (programName.isEmpty()) { + qCritical("No program defined for clipboard manipulation"); + return EXIT_FAILURE; + } + + QProcess* clipProcess = new QProcess(nullptr); + clipProcess->start(programName, arguments); + clipProcess->waitForStarted(); + + if (clipProcess->state() != QProcess::Running) { + qCritical("Unable to start program %s", qPrintable(programName)); + return EXIT_FAILURE; + } + + if (clipProcess->write(text.toLatin1()) == -1) { + qDebug("Unable to write to process : %s", qPrintable(clipProcess->errorString())); + } + clipProcess->waitForBytesWritten(); + clipProcess->closeWriteChannel(); + clipProcess->waitForFinished(); + + return clipProcess->exitCode(); +} diff --git a/src/cli/PasswordInput.h b/src/cli/Utils.h similarity index 85% rename from src/cli/PasswordInput.h rename to src/cli/Utils.h index b76061864..0c6b749a3 100644 --- a/src/cli/PasswordInput.h +++ b/src/cli/Utils.h @@ -15,17 +15,17 @@ * along with this program. If not, see . */ -#ifndef KEEPASSXC_PASSWORDINPUT_H -#define KEEPASSXC_PASSWORDINPUT_H +#ifndef KEEPASSXC_UTILS_H +#define KEEPASSXC_UTILS_H #include -class PasswordInput +class Utils { public: - PasswordInput(); static void setStdinEcho(bool enable); static QString getPassword(); + static int clipText(QString text); }; -#endif // KEEPASSXC_PASSWORDINPUT_H +#endif // KEEPASSXC_UTILS_H diff --git a/src/core/Database.cpp b/src/core/Database.cpp index d1c0fea4d..b0b215eb6 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -24,7 +24,7 @@ #include #include -#include "cli/PasswordInput.h" +#include "cli/Utils.h" #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Random.h" @@ -404,7 +404,7 @@ Database* Database::unlockFromStdin(QString databaseFilename) outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> "); outputTextStream.flush(); - QString line = PasswordInput::getPassword(); + QString line = Utils::getPassword(); CompositeKey key = CompositeKey::readFromLine(line); return Database::openDatabaseFile(databaseFilename, key); }