diff --git a/CMakeLists.txt b/CMakeLists.txt index fa25c96175..2520063f98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,16 +303,16 @@ endif() include(CLangFormat) if(UNIX AND NOT APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools DBus REQUIRED) + find_package(Qt5 COMPONENTS Core Network Concurrent Gui Widgets Test LinguistTools DBus REQUIRED) elseif(APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools REQUIRED + find_package(Qt5 COMPONENTS Core Network Concurrent Gui Widgets Test LinguistTools REQUIRED HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH ) find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH ) else() - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools REQUIRED) + find_package(Qt5 COMPONENTS Core Network Concurrent Gui Widgets Test LinguistTools REQUIRED) endif() if(Qt5Core_VERSION VERSION_LESS "5.2.0") diff --git a/Dockerfile b/Dockerfile index 89ee044647..f770547a1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ FROM ubuntu:14.04 -ENV REBUILD_COUNTER=8 +ENV REBUILD_COUNTER=9 ENV QT5_VERSION=qt510 ENV QT5_PPA_VERSION=qt-5.10.1 @@ -55,7 +55,9 @@ RUN set -x \ libxtst-dev \ mesa-common-dev \ libyubikey-dev \ - libykpers-1-dev + libykpers-1-dev \ + xclip \ + xvfb ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}" ENV CMAKE_PREFIX_PATH="/opt/${QT5_VERSION}/lib/cmake" diff --git a/ci/trusty/Dockerfile b/ci/trusty/Dockerfile index 04aee25a53..a9b9276d1d 100644 --- a/ci/trusty/Dockerfile +++ b/ci/trusty/Dockerfile @@ -39,6 +39,7 @@ RUN set -x \ clang-3.6 \ libclang-common-3.6-dev \ clang-format-3.6 \ + llvm-3.6 \ cmake3 \ make \ libgcrypt20-18-dev \ @@ -54,6 +55,7 @@ RUN set -x \ libykpers-1-dev \ libxi-dev \ libxtst-dev \ + xclip \ xvfb ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3621067e81..4df7245743 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(keepassx_SOURCES core/EntryAttributes.cpp core/EntrySearcher.cpp core/FilePath.cpp + core/Bootstrap.cpp core/Global.h core/Group.cpp core/InactivityTimer.cpp diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 60cf8506a8..f9e1845023 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -271,7 +271,6 @@ QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const { const QString nonce = json.value("nonce").toString(); const QString password = browserSettings()->generatePassword(); - const QString bits = QString::number(browserSettings()->getbits()); // For some reason this always returns 1140 bits? if (nonce.isEmpty() || password.isEmpty()) { return QJsonObject(); diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index f7abdece98..96d8f98d7b 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -485,11 +485,6 @@ QString BrowserSettings::generatePassword() } } -int BrowserSettings::getbits() -{ - return m_passwordGenerator.getbits(); -} - void BrowserSettings::updateBinaryPaths(QString customProxyLocation) { bool isProxy = supportBrowserProxy(); diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index 3e84ba37dd..5dc28593a1 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -112,7 +112,6 @@ class BrowserSettings PasswordGenerator::CharClasses passwordCharClasses(); PasswordGenerator::GeneratorFlags passwordGeneratorFlags(); QString generatePassword(); - int getbits(); void updateBinaryPaths(QString customProxyLocation = QString()); private: diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 5c97299b82..81a5cad135 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -41,22 +41,20 @@ Add::~Add() int Add::execute(const QStringList& arguments) { - - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); + QTextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - QCommandLineOption username(QStringList() << "u" - << "username", + QCommandLineOption username(QStringList() << "u" << "username", QObject::tr("Username for the entry."), QObject::tr("username")); parser.addOption(username); @@ -64,23 +62,22 @@ int Add::execute(const QStringList& arguments) QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL")); parser.addOption(url); - QCommandLineOption prompt(QStringList() << "p" - << "password-prompt", + QCommandLineOption prompt(QStringList() << "p" << "password-prompt", QObject::tr("Prompt for the entry's password.")); parser.addOption(prompt); - QCommandLineOption generate(QStringList() << "g" - << "generate", + QCommandLineOption generate(QStringList() << "g" << "generate", QObject::tr("Generate a password for the entry.")); parser.addOption(generate); - QCommandLineOption length(QStringList() << "l" - << "password-length", + QCommandLineOption length(QStringList() << "l" << "password-length", QObject::tr("Length for the generated password."), QObject::tr("length")); parser.addOption(length); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to add.")); + + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -89,11 +86,11 @@ int Add::execute(const QStringList& arguments) return EXIT_FAILURE; } - QString databasePath = args.at(0); - QString entryPath = args.at(1); + const QString& databasePath = args.at(0); + const QString& entryPath = args.at(1); - Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile)); - if (db == nullptr) { + Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR); + if (!db) { return EXIT_FAILURE; } @@ -101,13 +98,13 @@ int Add::execute(const QStringList& arguments) // the entry. QString passwordLength = parser.value(length); if (!passwordLength.isEmpty() && !passwordLength.toInt()) { - qCritical("Invalid value for password length %s.", qPrintable(passwordLength)); + errorTextStream << QObject::tr("Invalid value for password length %1.").arg(passwordLength) << endl; return EXIT_FAILURE; } Entry* entry = db->rootGroup()->addEntryWithPath(entryPath); if (!entry) { - qCritical("Could not create entry with path %s.", qPrintable(entryPath)); + errorTextStream << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -120,8 +117,7 @@ int Add::execute(const QStringList& arguments) } if (parser.isSet(prompt)) { - outputTextStream << "Enter password for new entry: "; - outputTextStream.flush(); + outputTextStream << QObject::tr("Enter password for new entry: ") << flush; QString password = Utils::getPassword(); entry->setPassword(password); } else if (parser.isSet(generate)) { @@ -130,7 +126,7 @@ int Add::execute(const QStringList& arguments) if (passwordLength.isEmpty()) { passwordGenerator.setLength(PasswordGenerator::DefaultLength); } else { - passwordGenerator.setLength(passwordLength.toInt()); + passwordGenerator.setLength(static_cast(passwordLength.toInt())); } passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); @@ -141,10 +137,10 @@ int Add::execute(const QStringList& arguments) QString errorMessage = db->saveToFile(databasePath); if (!errorMessage.isEmpty()) { - qCritical("Writing the database failed %s.", qPrintable(errorMessage)); + errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << "Successfully added entry " << entry->title() << "." << endl; + outputTextStream << QObject::tr("Successfully added entry %1.").arg(entry->title()) << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 886f8ecc71..0b78a24b40 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -42,20 +42,19 @@ Clip::~Clip() int Clip::execute(const QStringList& arguments) { - - QTextStream out(stdout); + QTextStream out(Utils::STDOUT); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip.", "clip = copy to clipboard")); - parser.addPositionalArgument( - "timeout", QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]")); + parser.addPositionalArgument("timeout", + QObject::tr("Timeout in seconds before clearing the clipboard."), "[timeout]"); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -64,29 +63,30 @@ int Clip::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); + Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR); if (!db) { return EXIT_FAILURE; } - return this->clipEntry(db, args.at(1), args.value(2)); + return clipEntry(db, args.at(1), args.value(2)); } int Clip::clipEntry(Database* database, QString entryPath, QString timeout) { + QTextStream err(Utils::STDERR); int timeoutSeconds = 0; if (!timeout.isEmpty() && !timeout.toInt()) { - qCritical("Invalid timeout value %s.", qPrintable(timeout)); + err << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl; return EXIT_FAILURE; } else if (!timeout.isEmpty()) { timeoutSeconds = timeout.toInt(); } - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntry(entryPath); if (!entry) { - qCritical("Entry %s not found.", qPrintable(entryPath)); + err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -95,20 +95,23 @@ int Clip::clipEntry(Database* database, QString entryPath, QString timeout) return exitCode; } - outputTextStream << "Entry's password copied to the clipboard!" << endl; + outputTextStream << QObject::tr("Entry's password copied to the clipboard!") << endl; if (!timeoutSeconds) { return exitCode; } + QString lastLine = ""; while (timeoutSeconds > 0) { - outputTextStream << "\rClearing the clipboard in " << timeoutSeconds << " seconds..."; - outputTextStream.flush(); + outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; + lastLine = QObject::tr("Clearing the clipboard in %1 second(s)...", "", timeoutSeconds).arg(timeoutSeconds); + outputTextStream << lastLine << flush; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - timeoutSeconds--; + --timeoutSeconds; } Utils::clipText(""); - outputTextStream << "\nClipboard cleared!" << endl; + outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; + outputTextStream << QObject::tr("Clipboard cleared!") << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index ef69488889..c85e5d95de 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -41,19 +41,14 @@ Command::~Command() { } -int Command::execute(const QStringList&) -{ - return EXIT_FAILURE; -} - QString Command::getDescriptionLine() { - QString response = this->name; + QString response = name; QString space(" "); - QString spaces = space.repeated(15 - this->name.length()); + QString spaces = space.repeated(15 - name.length()); response = response.append(spaces); - response = response.append(this->description); + response = response.append(description); response = response.append("\n"); return response; } diff --git a/src/cli/Command.h b/src/cli/Command.h index 2ebdd77b9b..7ad49440aa 100644 --- a/src/cli/Command.h +++ b/src/cli/Command.h @@ -29,7 +29,7 @@ class Command { public: virtual ~Command(); - virtual int execute(const QStringList& arguments); + virtual int execute(const QStringList& arguments) = 0; QString name; QString description; QString getDescriptionLine(); diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index 4cdda0a73a..72cbd19605 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -24,6 +24,7 @@ #include #include "core/PassphraseGenerator.h" +#include "Utils.h" Diceware::Diceware() { @@ -37,26 +38,25 @@ Diceware::~Diceware() int Diceware::execute(const QStringList& arguments) { - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); - QCommandLineOption words(QStringList() << "W" - << "words", + parser.setApplicationDescription(description); + QCommandLineOption words(QStringList() << "W" << "words", QObject::tr("Word count for the diceware passphrase."), - QObject::tr("count")); + QObject::tr("count", "CLI parameter")); parser.addOption(words); - QCommandLineOption wordlistFile(QStringList() << "w" - << "word-list", + QCommandLineOption wordlistFile(QStringList() << "w" << "word-list", QObject::tr("Wordlist for the diceware generator.\n[Default: EFF English]"), QObject::tr("path")); parser.addOption(wordlistFile); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (!args.isEmpty()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } @@ -76,12 +76,12 @@ int Diceware::execute(const QStringList& arguments) } if (!dicewareGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } QString password = dicewareGenerator.generatePassphrase(); - outputTextStream << password << endl; + out << password << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 056a0a595a..c2f0677941 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -41,22 +41,20 @@ Edit::~Edit() int Edit::execute(const QStringList& arguments) { - - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - QCommandLineOption username(QStringList() << "u" - << "username", + QCommandLineOption username(QStringList() << "u" << "username", QObject::tr("Username for the entry."), QObject::tr("username")); parser.addOption(username); @@ -64,61 +62,58 @@ int Edit::execute(const QStringList& arguments) QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL")); parser.addOption(url); - QCommandLineOption title(QStringList() << "t" - << "title", + QCommandLineOption title(QStringList() << "t" << "title", QObject::tr("Title for the entry."), QObject::tr("title")); parser.addOption(title); - QCommandLineOption prompt(QStringList() << "p" - << "password-prompt", + QCommandLineOption prompt(QStringList() << "p" << "password-prompt", QObject::tr("Prompt for the entry's password.")); parser.addOption(prompt); - QCommandLineOption generate(QStringList() << "g" - << "generate", + QCommandLineOption generate(QStringList() << "g" << "generate", QObject::tr("Generate a password for the entry.")); parser.addOption(generate); - QCommandLineOption length(QStringList() << "l" - << "password-length", + QCommandLineOption length(QStringList() << "l" << "password-length", QObject::tr("Length for the generated password."), QObject::tr("length")); parser.addOption(length); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to edit.")); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); return EXIT_FAILURE; } - QString databasePath = args.at(0); - QString entryPath = args.at(1); + const QString& databasePath = args.at(0); + const QString& entryPath = args.at(1); - Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile)); - if (db == nullptr) { + Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR); + if (!db) { return EXIT_FAILURE; } QString passwordLength = parser.value(length); if (!passwordLength.isEmpty() && !passwordLength.toInt()) { - qCritical("Invalid value for password length %s.", qPrintable(passwordLength)); + err << QObject::tr("Invalid value for password length: %1").arg(passwordLength) << endl; return EXIT_FAILURE; } Entry* entry = db->rootGroup()->findEntryByPath(entryPath); if (!entry) { - qCritical("Could not find entry with path %s.", qPrintable(entryPath)); + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } if (parser.value("username").isEmpty() && parser.value("url").isEmpty() && parser.value("title").isEmpty() && !parser.isSet(prompt) && !parser.isSet(generate)) { - qCritical("Not changing any field for entry %s.", qPrintable(entryPath)); + err << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -137,8 +132,7 @@ int Edit::execute(const QStringList& arguments) } if (parser.isSet(prompt)) { - outputTextStream << "Enter new password for entry: "; - outputTextStream.flush(); + out << QObject::tr("Enter new password for entry: ") << flush; QString password = Utils::getPassword(); entry->setPassword(password); } else if (parser.isSet(generate)) { @@ -147,7 +141,7 @@ int Edit::execute(const QStringList& arguments) if (passwordLength.isEmpty()) { passwordGenerator.setLength(PasswordGenerator::DefaultLength); } else { - passwordGenerator.setLength(passwordLength.toInt()); + passwordGenerator.setLength(static_cast(passwordLength.toInt())); } passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); @@ -160,10 +154,10 @@ int Edit::execute(const QStringList& arguments) QString errorMessage = db->saveToFile(databasePath); if (!errorMessage.isEmpty()) { - qCritical("Writing the database failed %s.", qPrintable(errorMessage)); + err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << "Successfully edited entry " << entry->title() << "." << endl; + out << QObject::tr("Successfully edited entry %1.").arg(entry->title()) << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Estimate.cpp b/src/cli/Estimate.cpp index 9a2ab0b0f7..c249d7b1f0 100644 --- a/src/cli/Estimate.cpp +++ b/src/cli/Estimate.cpp @@ -16,6 +16,7 @@ */ #include "Estimate.h" +#include "cli/Utils.h" #include #include @@ -44,117 +45,126 @@ Estimate::~Estimate() static void estimate(const char* pwd, bool advanced) { - double e; - int len = strlen(pwd); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + + double e = 0.0; + int len = static_cast(strlen(pwd)); if (!advanced) { - e = ZxcvbnMatch(pwd, 0, 0); - printf("Length %d\tEntropy %.3f\tLog10 %.3f\n", len, e, e * 0.301029996); + e = ZxcvbnMatch(pwd, nullptr, nullptr); + out << QObject::tr("Length %1").arg(len, 0) << '\t' + << QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t' + << QObject::tr("Log10 %1").arg(e * 0.301029996, 0, 'f', 3) << endl; } else { - int ChkLen; + int ChkLen = 0; ZxcMatch_t *info, *p; double m = 0.0; - e = ZxcvbnMatch(pwd, 0, &info); + e = ZxcvbnMatch(pwd, nullptr, &info); for (p = info; p; p = p->Next) { m += p->Entrpy; } m = e - m; - printf("Length %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", len, e, e * 0.301029996, m); + out << QObject::tr("Length %1").arg(len) << '\t' + << QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t' + << QObject::tr("Log10 %1").arg(e * 0.301029996, 0, 'f', 3) << "\n " + << QObject::tr("Multi-word extra bits %1").arg(m, 0, 'f', 1) << endl; p = info; ChkLen = 0; while (p) { int n; switch (static_cast(p->Type)) { case BRUTE_MATCH: - printf(" Type: Bruteforce "); + out << " " << QObject::tr("Type: Bruteforce") << " "; break; case DICTIONARY_MATCH: - printf(" Type: Dictionary "); + out << " " << QObject::tr("Type: Dictionary") << " "; break; case DICT_LEET_MATCH: - printf(" Type: Dict+Leet "); + out << " " << QObject::tr("Type: Dict+Leet") << " "; break; case USER_MATCH: - printf(" Type: User Words "); + out << " " << QObject::tr("Type: User Words") << " "; break; case USER_LEET_MATCH: - printf(" Type: User+Leet "); + out << " " << QObject::tr("Type: User+Leet") << " "; break; case REPEATS_MATCH: - printf(" Type: Repeated "); + out << " " << QObject::tr("Type: Repeated") << " "; break; case SEQUENCE_MATCH: - printf(" Type: Sequence "); + out << " " << QObject::tr("Type: Sequence") << " "; break; case SPATIAL_MATCH: - printf(" Type: Spatial "); + out << " " << QObject::tr("Type: Spatial") << " "; break; case DATE_MATCH: - printf(" Type: Date "); + out << " " << QObject::tr("Type: Date") << " "; break; case BRUTE_MATCH + MULTIPLE_MATCH: - printf(" Type: Bruteforce(Rep)"); + out << " " << QObject::tr("Type: Bruteforce(Rep)") << " "; break; case DICTIONARY_MATCH + MULTIPLE_MATCH: - printf(" Type: Dictionary(Rep)"); + out << " " << QObject::tr("Type: Dictionary(Rep)") << " "; break; case DICT_LEET_MATCH + MULTIPLE_MATCH: - printf(" Type: Dict+Leet(Rep) "); + out << " " << QObject::tr("Type: Dict+Leet(Rep)") << " "; break; case USER_MATCH + MULTIPLE_MATCH: - printf(" Type: User Words(Rep)"); + out << " " << QObject::tr("Type: User Words(Rep)") << " "; break; case USER_LEET_MATCH + MULTIPLE_MATCH: - printf(" Type: User+Leet(Rep) "); + out << " " << QObject::tr("Type: User+Leet(Rep)") << " "; break; case REPEATS_MATCH + MULTIPLE_MATCH: - printf(" Type: Repeated(Rep) "); + out << " " << QObject::tr("Type: Repeated(Rep)") << " "; break; case SEQUENCE_MATCH + MULTIPLE_MATCH: - printf(" Type: Sequence(Rep) "); + out << " " << QObject::tr("Type: Sequence(Rep)") << " "; break; case SPATIAL_MATCH + MULTIPLE_MATCH: - printf(" Type: Spatial(Rep) "); + out << " " << QObject::tr("Type: Spatial(Rep)") << " "; break; case DATE_MATCH + MULTIPLE_MATCH: - printf(" Type: Date(Rep) "); + out << " " << QObject::tr("Type: Date(Rep)") << " "; break; default: - printf(" Type: Unknown%d ", p->Type); + out << " " << QObject::tr("Type: Unknown%1").arg(p->Type) << " "; break; } ChkLen += p->Length; - printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996); + + out << QObject::tr("Length %1").arg(p->Length) << '\t' + << QObject::tr("Entropy %1 (%2)").arg(p->Entrpy, 6, 'f', 3).arg(p->Entrpy * 0.301029996, 0, 'f', 2) << '\t'; for (n = 0; n < p->Length; ++n, ++pwd) { - printf("%c", *pwd); + out << *pwd; } - printf("\n"); + out << endl; p = p->Next; } ZxcvbnFreeInfo(info); if (ChkLen != len) { - printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen); + out << QObject::tr("*** Password length (%1) != sum of length of parts (%2) ***").arg(len).arg(ChkLen) << endl; } } } int Estimate::execute(const QStringList& arguments) { - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("password", QObject::tr("Password for which to estimate the entropy."), "[password]"); - QCommandLineOption advancedOption(QStringList() << "a" - << "advanced", + QCommandLineOption advancedOption(QStringList() << "a" << "advanced", QObject::tr("Perform advanced analysis on the password.")); parser.addOption(advancedOption); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() > 1) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); return EXIT_FAILURE; } @@ -162,7 +172,7 @@ int Estimate::execute(const QStringList& arguments) if (args.size() == 1) { password = args.at(0); } else { - password = inputTextStream.readLine(); + password = in.readLine(); } estimate(password.toLatin1(), parser.isSet(advancedOption)); diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index f0004e6882..cc39c469ad 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -43,17 +43,17 @@ Extract::~Extract() int Extract::execute(const QStringList& arguments) { - QTextStream out(stdout); - QTextStream errorTextStream(stderr); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database to extract.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -62,8 +62,7 @@ int Extract::execute(const QStringList& arguments) return EXIT_FAILURE; } - out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)); - out.flush(); + out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)) << flush; auto compositeKey = QSharedPointer::create(); @@ -74,52 +73,51 @@ int Extract::execute(const QStringList& arguments) QString keyFilePath = parser.value(keyFile); if (!keyFilePath.isEmpty()) { + // LCOV_EXCL_START auto fileKey = QSharedPointer::create(); QString errorMsg; if (!fileKey->load(keyFilePath, &errorMsg)) { - errorTextStream << QObject::tr("Failed to load key file %1 : %2").arg(keyFilePath).arg(errorMsg); - errorTextStream << endl; + err << QObject::tr("Failed to load key file %1: %2").arg(keyFilePath).arg(errorMsg) << endl; return EXIT_FAILURE; } if (fileKey->type() != FileKey::Hashed) { - errorTextStream << QObject::tr("WARNING: You are using a legacy key file format which may become\n" - "unsupported in the future.\n\n" - "Please consider generating a new key file."); - errorTextStream << endl; + err << QObject::tr("WARNING: You are using a legacy key file format which may become\n" + "unsupported in the future.\n\n" + "Please consider generating a new key file.") << endl; } + // LCOV_EXCL_STOP compositeKey->addKey(fileKey); } - QString databaseFilename = args.at(0); + const QString& databaseFilename = args.at(0); QFile dbFile(databaseFilename); if (!dbFile.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename)); + err << QObject::tr("File %1 does not exist.").arg(databaseFilename) << endl; return EXIT_FAILURE; } if (!dbFile.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename)); + err << QObject::tr("Unable to open file %1.").arg(databaseFilename) << endl; return EXIT_FAILURE; } KeePass2Reader reader; reader.setSaveXml(true); - Database* db = reader.readDatabase(&dbFile, compositeKey); - delete db; + QScopedPointer db(reader.readDatabase(&dbFile, compositeKey)); QByteArray xmlData = reader.reader()->xmlData(); if (reader.hasError()) { if (xmlData.isEmpty()) { - qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString())); + err << QObject::tr("Error while reading the database:\n%1").arg(reader.errorString()) << endl; } else { - qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + err << QObject::tr("Error while parsing the database:\n%1").arg(reader.errorString()) << endl; } return EXIT_FAILURE; } - out << xmlData.constData() << "\n"; + out << xmlData.constData() << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index 6a5be3f07d..7780aa8294 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -19,6 +19,7 @@ #include #include "Generate.h" +#include "cli/Utils.h" #include #include @@ -37,38 +38,32 @@ Generate::~Generate() int Generate::execute(const QStringList& arguments) { - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + out.setCodec("UTF-8"); // force UTF-8 to prevent ??? characters in extended-ASCII passwords QCommandLineParser parser; - parser.setApplicationDescription(this->description); - QCommandLineOption len(QStringList() << "L" - << "length", + parser.setApplicationDescription(description); + QCommandLineOption len(QStringList() << "L" << "length", QObject::tr("Length of the generated password"), QObject::tr("length")); parser.addOption(len); - QCommandLineOption lower(QStringList() << "l" - << "lower", + QCommandLineOption lower(QStringList() << "l" << "lower", QObject::tr("Use lowercase characters")); parser.addOption(lower); - QCommandLineOption upper(QStringList() << "u" - << "upper", + QCommandLineOption upper(QStringList() << "u" << "upper", QObject::tr("Use uppercase characters")); parser.addOption(upper); - QCommandLineOption numeric(QStringList() << "n" - << "numeric", + QCommandLineOption numeric(QStringList() << "n" << "numeric", QObject::tr("Use numbers.")); parser.addOption(numeric); - QCommandLineOption special(QStringList() << "s" - << "special", + QCommandLineOption special(QStringList() << "s" << "special", QObject::tr("Use special characters")); parser.addOption(special); - QCommandLineOption extended(QStringList() << "e" - << "extended", + QCommandLineOption extended(QStringList() << "e" << "extended", QObject::tr("Use extended ASCII")); parser.addOption(extended); - QCommandLineOption exclude(QStringList() << "x" - << "exclude", + QCommandLineOption exclude(QStringList() << "x" << "exclude", QObject::tr("Exclude character set"), QObject::tr("chars")); parser.addOption(exclude); @@ -78,12 +73,12 @@ int Generate::execute(const QStringList& arguments) QCommandLineOption every_group(QStringList() << "every-group", QObject::tr("Include characters from every selected group")); parser.addOption(every_group); - + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (!args.isEmpty()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } @@ -93,7 +88,7 @@ int Generate::execute(const QStringList& arguments) passwordGenerator.setLength(PasswordGenerator::DefaultLength); } else { int length = parser.value(len).toInt(); - passwordGenerator.setLength(length); + passwordGenerator.setLength(static_cast(length)); } PasswordGenerator::CharClasses classes = 0x0; @@ -128,12 +123,12 @@ int Generate::execute(const QStringList& arguments) passwordGenerator.setExcludedChars(parser.value(exclude)); if (!passwordGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } QString password = passwordGenerator.generatePassword(); - outputTextStream << password << endl; + out << password << endl; return EXIT_SUCCESS; } diff --git a/src/cli/List.cpp b/src/cli/List.cpp index b39b3fc147..4d1ebcfc50 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -19,6 +19,7 @@ #include #include "List.h" +#include "cli/Utils.h" #include #include @@ -39,22 +40,20 @@ List::~List() int List::execute(const QStringList& arguments) { - QTextStream out(stdout); + QTextStream out(Utils::STDOUT); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), QString("[group]")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), "[group]"); + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - - QCommandLineOption recursiveOption(QStringList() << "R" - << "recursive", + QCommandLineOption recursiveOption(QStringList() << "R" << "recursive", QObject::tr("Recursive mode, list elements recursively")); parser.addOption(recursiveOption); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -65,33 +64,33 @@ int List::execute(const QStringList& arguments) bool recursive = parser.isSet(recursiveOption); - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db == nullptr) { + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db) { return EXIT_FAILURE; } if (args.size() == 2) { - return this->listGroup(db, recursive, args.at(1)); + return listGroup(db.data(), recursive, args.at(1)); } - return this->listGroup(db, recursive); + return listGroup(db.data(), recursive); } -int List::listGroup(Database* database, bool recursive, QString groupPath) +int List::listGroup(Database* database, bool recursive, const QString& groupPath) { - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); + if (groupPath.isEmpty()) { - outputTextStream << database->rootGroup()->print(recursive); - outputTextStream.flush(); + out << database->rootGroup()->print(recursive) << flush; return EXIT_SUCCESS; } Group* group = database->rootGroup()->findGroupByPath(groupPath); - if (group == nullptr) { - qCritical("Cannot find group %s.", qPrintable(groupPath)); + if (!group) { + err << QObject::tr("Cannot find group %1.").arg(groupPath) << endl; return EXIT_FAILURE; } - outputTextStream << group->print(recursive); - outputTextStream.flush(); + out << group->print(recursive) << flush; return EXIT_SUCCESS; } diff --git a/src/cli/List.h b/src/cli/List.h index 00c3769722..5697d93904 100644 --- a/src/cli/List.h +++ b/src/cli/List.h @@ -26,7 +26,7 @@ class List : public Command List(); ~List(); int execute(const QStringList& arguments); - int listGroup(Database* database, bool recursive, QString groupPath = QString("")); + int listGroup(Database* database, bool recursive, const QString& groupPath = {}); }; #endif // KEEPASSXC_LIST_H diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index f803728855..3bca8ae1da 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -1,3 +1,5 @@ +#include + /* * Copyright (C) 2017 KeePassXC Team * @@ -25,6 +27,7 @@ #include #include "cli/Utils.h" +#include "core/Global.h" #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" @@ -41,18 +44,17 @@ Locate::~Locate() int Locate::execute(const QStringList& arguments) { - - QTextStream out(stdout); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); parser.addPositionalArgument("term", QObject::tr("Search term.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -61,26 +63,27 @@ int Locate::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); if (!db) { return EXIT_FAILURE; } - return this->locateEntry(db, args.at(1)); + return locateEntry(db.data(), args.at(1)); } -int Locate::locateEntry(Database* database, QString searchTerm) +int Locate::locateEntry(Database* database, const QString& searchTerm) { + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); QStringList results = database->rootGroup()->locate(searchTerm); if (results.isEmpty()) { - outputTextStream << "No results for that search term" << endl; - return EXIT_SUCCESS; + err << "No results for that search term." << endl; + return EXIT_FAILURE; } - for (QString result : results) { - outputTextStream << result << endl; + for (const QString& result : asConst(results)) { + out << result << endl; } return EXIT_SUCCESS; } diff --git a/src/cli/Locate.h b/src/cli/Locate.h index 3677a034df..3355d41ec6 100644 --- a/src/cli/Locate.h +++ b/src/cli/Locate.h @@ -26,7 +26,7 @@ class Locate : public Command Locate(); ~Locate(); int execute(const QStringList& arguments); - int locateEntry(Database* database, QString searchTerm); + int locateEntry(Database* database, const QString& searchTerm); }; #endif // KEEPASSXC_LOCATE_H diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index ea7e6636a2..a5b4a2cb7e 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -15,8 +15,6 @@ * along with this program. If not, see . */ -#include - #include "Merge.h" #include @@ -24,6 +22,9 @@ #include "core/Database.h" #include "core/Merger.h" +#include "cli/Utils.h" + +#include Merge::Merge() { @@ -37,29 +38,28 @@ Merge::~Merge() int Merge::execute(const QStringList& arguments) { - QTextStream out(stdout); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database1", QObject::tr("Path of the database to merge into.")); parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from.")); - QCommandLineOption samePasswordOption(QStringList() << "s" - << "same-credentials", + QCommandLineOption samePasswordOption(QStringList() << "s" << "same-credentials", QObject::tr("Use the same credentials for both database files.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - QCommandLineOption keyFileFrom(QStringList() << "f" - << "key-file-from", + QCommandLineOption keyFileFrom(QStringList() << "f" << "key-file-from", QObject::tr("Key file of the database to merge from."), QObject::tr("path")); parser.addOption(keyFileFrom); parser.addOption(samePasswordOption); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -68,30 +68,30 @@ int Merge::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db1 == nullptr) { + QScopedPointer db1(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db1) { return EXIT_FAILURE; } - Database* db2; + QScopedPointer db2; if (!parser.isSet("same-credentials")) { - db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom)); + db2.reset(Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR)); } else { - db2 = Database::openDatabaseFile(args.at(1), db1->key()); + db2.reset(Database::openDatabaseFile(args.at(1), db1->key())); } - if (db2 == nullptr) { + if (!db2) { return EXIT_FAILURE; } - Merger merger(db2, db1); + Merger merger(db2.data(), db1.data()); merger.merge(); QString errorMessage = db1->saveToFile(args.at(0)); if (!errorMessage.isEmpty()) { - qCritical("Unable to save database to file : %s", qPrintable(errorMessage)); + err << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - out << "Successfully merged the database files.\n"; + out << "Successfully merged the database files." << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index 64a5976e9a..5bbfd67e43 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -44,40 +44,41 @@ Remove::~Remove() int Remove::execute(const QStringList& arguments) { - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream out(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "Remove an entry from the database.")); - parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + parser.setApplicationDescription(QCoreApplication::tr("main", "Remove an entry from the database.")); + parser.addPositionalArgument("database", QCoreApplication::tr("main", "Path of the database.")); + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Path of the entry to remove.")); + parser.addPositionalArgument("entry", QCoreApplication::tr("main", "Path of the entry to remove.")); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db == nullptr) { + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db) { return EXIT_FAILURE; } - return this->removeEntry(db, args.at(0), args.at(1)); + return removeEntry(db.data(), args.at(0), args.at(1)); } -int Remove::removeEntry(Database* database, QString databasePath, QString entryPath) +int Remove::removeEntry(Database* database, const QString& databasePath, const QString& entryPath) { + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - qCritical("Entry %s not found.", qPrintable(entryPath)); + err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -92,14 +93,14 @@ int Remove::removeEntry(Database* database, QString databasePath, QString entryP QString errorMessage = database->saveToFile(databasePath); if (!errorMessage.isEmpty()) { - qCritical("Unable to save database to file : %s", qPrintable(errorMessage)); + err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } if (recycled) { - outputTextStream << "Successfully recycled entry " << entryTitle << "." << endl; + out << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl; } else { - outputTextStream << "Successfully deleted entry " << entryTitle << "." << endl; + out << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl; } return EXIT_SUCCESS; diff --git a/src/cli/Remove.h b/src/cli/Remove.h index 5465530eda..33d62f4bd6 100644 --- a/src/cli/Remove.h +++ b/src/cli/Remove.h @@ -28,7 +28,7 @@ class Remove : public Command Remove(); ~Remove(); int execute(const QStringList& arguments); - int removeEntry(Database* database, QString databasePath, QString entryPath); + int removeEntry(Database* database, const QString& databasePath, const QString& entryPath); }; #endif // KEEPASSXC_REMOVE_H diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index be188c75c9..5e2ec14b4f 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -15,17 +15,19 @@ * along with this program. If not, see . */ +#include "Show.h" + #include #include -#include "Show.h" - #include #include #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" +#include "core/Global.h" +#include "Utils.h" Show::Show() { @@ -39,19 +41,17 @@ Show::~Show() int Show::execute(const QStringList& arguments) { - QTextStream out(stdout); + QTextStream out(Utils::STDOUT); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); QCommandLineOption attributes( - QStringList() << "a" - << "attributes", + QStringList() << "a" << "attributes", QObject::tr( "Names of the attributes to show. " "This option can be specified more than once, with each attribute shown one-per-line in the given order. " @@ -59,6 +59,7 @@ int Show::execute(const QStringList& arguments) QObject::tr("attribute")); parser.addOption(attributes); parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show.")); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -67,23 +68,23 @@ int Show::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db == nullptr) { + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db) { return EXIT_FAILURE; } - return this->showEntry(db, parser.values(attributes), args.at(1)); + return showEntry(db.data(), parser.values(attributes), args.at(1)); } -int Show::showEntry(Database* database, QStringList attributes, QString entryPath) +int Show::showEntry(Database* database, QStringList attributes, const QString& entryPath) { - - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntry(entryPath); if (!entry) { - qCritical("Could not find entry with path %s.", qPrintable(entryPath)); + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -95,16 +96,16 @@ int Show::showEntry(Database* database, QStringList attributes, QString entryPat // Iterate over the attributes and output them line-by-line. bool sawUnknownAttribute = false; - for (QString attribute : attributes) { + for (const QString& attribute : asConst(attributes)) { if (!entry->attributes()->contains(attribute)) { sawUnknownAttribute = true; - qCritical("ERROR: unknown attribute '%s'.", qPrintable(attribute)); + err << QObject::tr("ERROR: unknown attribute %1.").arg(attribute) << endl; continue; } if (showAttributeNames) { - outputTextStream << attribute << ": "; + out << attribute << ": "; } - outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl; + out << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl; } return sawUnknownAttribute ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/cli/Show.h b/src/cli/Show.h index 18b6d7049e..fe16546c3b 100644 --- a/src/cli/Show.h +++ b/src/cli/Show.h @@ -26,7 +26,7 @@ class Show : public Command Show(); ~Show(); int execute(const QStringList& arguments); - int showEntry(Database* database, QStringList attributes, QString entryPath); + int showEntry(Database* database, QStringList attributes, const QString& entryPath); }; #endif // KEEPASSXC_SHOW_H diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index 35e7cce389..8a0f5abe3f 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -25,9 +25,25 @@ #endif #include -#include -void Utils::setStdinEcho(bool enable = true) +namespace Utils +{ +/** + * STDOUT file handle for the CLI. + */ +FILE* STDOUT = stdout; + +/** + * STDERR file handle for the CLI. + */ +FILE* STDERR = stderr; + +/** + * STDIN file handle for the CLI. + */ +FILE* STDIN = stdin; + +void setStdinEcho(bool enable = true) { #ifdef Q_OS_WIN HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); @@ -56,28 +72,55 @@ void Utils::setStdinEcho(bool enable = true) #endif } -QString Utils::getPassword() +QStringList nextPasswords = {}; + +/** + * Set the next password returned by \link getPassword() instead of reading it from STDIN. + * Multiple calls to this method will fill a queue of passwords. + * This function is intended for testing purposes. + * + * @param password password to return next + */ +void setNextPassword(const QString& password) +{ + nextPasswords.append(password); +} + +/** + * Read a user password from STDIN or return a password previously + * set by \link setNextPassword(). + * + * @return the password + */ +QString getPassword() { - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - static QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream out(STDOUT, QIODevice::WriteOnly); + + // return preset password if one is set + if (!nextPasswords.isEmpty()) { + auto password = nextPasswords.takeFirst(); + // simulate user entering newline + out << endl; + return password; + } + + QTextStream in(STDIN, QIODevice::ReadOnly); setStdinEcho(false); - QString line = inputTextStream.readLine(); + QString line = in.readLine(); setStdinEcho(true); - - // The new line was also not echoed, but we do want to echo it. - outputTextStream << "\n"; - outputTextStream.flush(); + out << endl; 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(const QString& text) +int clipText(const QString& text) { + QTextStream err(Utils::STDERR); QString programName = ""; QStringList arguments; @@ -98,16 +141,18 @@ int Utils::clipText(const QString& text) #endif if (programName.isEmpty()) { - qCritical("No program defined for clipboard manipulation"); + err << QObject::tr("No program defined for clipboard manipulation"); + err.flush(); return EXIT_FAILURE; } - QProcess* clipProcess = new QProcess(nullptr); + auto* clipProcess = new QProcess(nullptr); clipProcess->start(programName, arguments); clipProcess->waitForStarted(); if (clipProcess->state() != QProcess::Running) { - qCritical("Unable to start program %s", qPrintable(programName)); + err << QObject::tr("Unable to start program %1").arg(programName); + err.flush(); return EXIT_FAILURE; } @@ -120,3 +165,5 @@ int Utils::clipText(const QString& text) return clipProcess->exitCode(); } + +} // namespace Utils diff --git a/src/cli/Utils.h b/src/cli/Utils.h index 1f80511833..868ccdef5b 100644 --- a/src/cli/Utils.h +++ b/src/cli/Utils.h @@ -19,13 +19,18 @@ #define KEEPASSXC_UTILS_H #include +#include -class Utils +namespace Utils { -public: - static void setStdinEcho(bool enable); - static QString getPassword(); - static int clipText(const QString& text); +extern FILE* STDOUT; +extern FILE* STDERR; +extern FILE* STDIN; + +void setStdinEcho(bool enable); +QString getPassword(); +void setNextPassword(const QString& password); +int clipText(const QString& text); }; #endif // KEEPASSXC_UTILS_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 1462f92b91..97efd8c08f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -25,7 +25,7 @@ #include #include "config-keepassx.h" -#include "core/Tools.h" +#include "core/Bootstrap.h" #include "crypto/Crypto.h" #if defined(WITH_ASAN) && defined(WITH_LSAN) @@ -34,17 +34,17 @@ int main(int argc, char** argv) { -#ifdef QT_NO_DEBUG - Tools::disableCoreDumps(); -#endif - if (!Crypto::init()) { qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); return EXIT_FAILURE; } QCoreApplication app(argc, argv); - app.setApplicationVersion(KEEPASSX_VERSION); + QCoreApplication::setApplicationVersion(KEEPASSX_VERSION); + +#ifdef QT_NO_DEBUG + Bootstrap::bootstrapApplication(); +#endif QTextStream out(stdout); QStringList arguments; @@ -69,7 +69,7 @@ int main(int argc, char** argv) // recognized by this parser. parser.parse(arguments); - if (parser.positionalArguments().size() < 1) { + if (parser.positionalArguments().empty()) { if (parser.isSet("version")) { // Switch to parser.showVersion() when available (QT 5.4). out << KEEPASSX_VERSION << endl; diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp new file mode 100644 index 0000000000..2c25b25056 --- /dev/null +++ b/src/core/Bootstrap.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018 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 "Bootstrap.h" +#include "core/Config.h" +#include "core/Translator.h" + +#ifdef Q_OS_WIN +#include // for createWindowsDACL() +#include // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ... +#endif + +namespace Bootstrap +{ +/** + * When QNetworkAccessManager is instantiated it regularly starts polling + * all network interfaces to see if anything changes and if so, what. This + * creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >= + * when on a wifi connection. + * So here we disable it for lack of better measure. + * This will also cause this message: QObject::startTimer: Timers cannot + * have negative intervals + * For more info see: + * - https://bugreports.qt.io/browse/QTBUG-40332 + * - https://bugreports.qt.io/browse/QTBUG-46015 + */ +static inline void applyEarlyQNetworkAccessManagerWorkaround() +{ + qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); +} + +/** + * Perform early application bootstrapping such as setting up search paths, + * configuration OS security properties, and loading translators. + * A QApplication object has to be instantiated before calling this function. + */ +void bootstrapApplication() +{ +#ifdef QT_NO_DEBUG + disableCoreDumps(); +#endif + setupSearchPaths(); + applyEarlyQNetworkAccessManagerWorkaround(); + Translator::installTranslators(); + +#ifdef Q_OS_MAC + // Don't show menu icons on OSX + QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); +#endif +} + +/** + * Restore the main window's state after launch + * + * @param mainWindow the main window whose state to restore + */ +void restoreMainWindowState(MainWindow& mainWindow) +{ + // start minimized if configured + bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool(); + bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool(); +#ifndef Q_OS_LINUX + if (minimizeOnStartup) { +#else + // On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at + // the same time (which would happen if both minimize on startup and minimize to tray are set) + // since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough. + if (minimizeOnStartup && !minimizeToTray) { +#endif + mainWindow.setWindowState(Qt::WindowMinimized); + } + if (!(minimizeOnStartup && minimizeToTray)) { + mainWindow.show(); + } + + if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { + const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList(); + for (const QString& filename : fileNames) { + if (!filename.isEmpty() && QFile::exists(filename)) { + mainWindow.openDatabase(filename); + } + } + } +} + +// LCOV_EXCL_START +void disableCoreDumps() +{ + // default to true + // there is no point in printing a warning if this is not implemented on the platform + bool success = true; + +#if defined(HAVE_RLIMIT_CORE) + struct rlimit limit; + limit.rlim_cur = 0; + limit.rlim_max = 0; + success = success && (setrlimit(RLIMIT_CORE, &limit) == 0); +#endif + +#if defined(HAVE_PR_SET_DUMPABLE) + success = success && (prctl(PR_SET_DUMPABLE, 0) == 0); +#endif + +// Mac OS X +#ifdef HAVE_PT_DENY_ATTACH + success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0); +#endif + +#ifdef Q_OS_WIN + success = success && createWindowsDACL(); +#endif + + if (!success) { + qWarning("Unable to disable core dumps."); + } +} + +// +// This function grants the user associated with the process token minimal access rights and +// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and +// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory(). +// We do this using a discretionary access control list (DACL). Effectively this prevents +// crash dumps and disallows other processes from accessing our memory. This works as long +// as you do not have admin privileges, since then you are able to grant yourself the +// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL. +// +bool createWindowsDACL() +{ + bool bSuccess = false; + +#ifdef Q_OS_WIN + // Process token and user + HANDLE hToken = nullptr; + PTOKEN_USER pTokenUser = nullptr; + DWORD cbBufferSize = 0; + + // Access control list + PACL pACL = nullptr; + DWORD cbACL = 0; + + // Open the access token associated with the calling process + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + goto Cleanup; + } + + // Retrieve the token information in a TOKEN_USER structure + GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize); + + pTokenUser = static_cast(HeapAlloc(GetProcessHeap(), 0, cbBufferSize)); + if (pTokenUser == nullptr) { + goto Cleanup; + } + + if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) { + goto Cleanup; + } + + if (!IsValidSid(pTokenUser->User.Sid)) { + goto Cleanup; + } + + // Calculate the amount of memory that must be allocated for the DACL + cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid); + + // Create and initialize an ACL + pACL = static_cast(HeapAlloc(GetProcessHeap(), 0, cbACL)); + if (pACL == nullptr) { + goto Cleanup; + } + + if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) { + goto Cleanup; + } + + // Add allowed access control entries, everything else is denied + if (!AddAccessAllowedAce( + pACL, + ACL_REVISION, + SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process + pTokenUser->User.Sid // pointer to the trustee's SID + )) { + goto Cleanup; + } + + // Set discretionary access control list + bSuccess = ERROR_SUCCESS + == SetSecurityInfo(GetCurrentProcess(), // object handle + SE_KERNEL_OBJECT, // type of object + DACL_SECURITY_INFORMATION, // change only the objects DACL + nullptr, + nullptr, // do not change owner or group + pACL, // DACL specified + nullptr // do not change SACL + ); + +Cleanup: + + if (pACL != nullptr) { + HeapFree(GetProcessHeap(), 0, pACL); + } + if (pTokenUser != nullptr) { + HeapFree(GetProcessHeap(), 0, pTokenUser); + } + if (hToken != nullptr) { + CloseHandle(hToken); + } +#endif + + return bSuccess; +} +// LCOV_EXCL_STOP + +void setupSearchPaths() +{ +#ifdef Q_OS_WIN + // Make sure Windows doesn't load DLLs from the current working directory + SetDllDirectoryA(""); + SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE); +#endif +} + +} // namespace Bootstrap diff --git a/src/core/Bootstrap.h b/src/core/Bootstrap.h new file mode 100644 index 0000000000..0e9db155a3 --- /dev/null +++ b/src/core/Bootstrap.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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_BOOTSTRAP_H +#define KEEPASSXC_BOOTSTRAP_H + +#include "gui/MainWindow.h" + +namespace Bootstrap +{ +void bootstrapApplication(); +void restoreMainWindowState(MainWindow& mainWindow); +void disableCoreDumps(); +bool createWindowsDACL(); +void setupSearchPaths(); +}; + + +#endif //KEEPASSXC_BOOTSTRAP_H diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5b7a3c07d2..5116fd1996 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -47,7 +47,7 @@ Database::Database() , m_emitModified(false) , m_uuid(QUuid::createUuid()) { - m_data.cipher = KeePass2::CIPHER_AES; + m_data.cipher = KeePass2::CIPHER_AES256; m_data.compressionAlgo = CompressionGZip; // instantiate default AES-KDF with legacy KDBX3 flag set @@ -501,14 +501,14 @@ Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer::create(); - QTextStream outputTextStream(stdout); - QTextStream errorTextStream(stderr); + QTextStream out(outputDescriptor); + QTextStream err(errorDescriptor); - outputTextStream << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename); - outputTextStream.flush(); + out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename); + out.flush(); QString line = Utils::getPassword(); auto passwordKey = QSharedPointer::create(); @@ -518,11 +518,19 @@ Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilenam if (!keyFilename.isEmpty()) { auto fileKey = QSharedPointer::create(); QString errorMessage; + // LCOV_EXCL_START if (!fileKey->load(keyFilename, &errorMessage)) { - errorTextStream << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage); - errorTextStream << endl; + err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl; return nullptr; } + + if (fileKey->type() != FileKey::Hashed) { + err << QObject::tr("WARNING: You are using a legacy key file format which may become\n" + "unsupported in the future.\n\n" + "Please consider generating a new key file.") << endl; + } + // LCOV_EXCL_STOP + compositeKey->addKey(fileKey); } diff --git a/src/core/Database.h b/src/core/Database.h index a5ae3effad..7108ded310 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -131,7 +131,8 @@ class Database : public QObject static Database* databaseByUuid(const QUuid& uuid); static Database* openDatabaseFile(const QString& fileName, QSharedPointer key); - static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = QString("")); + static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = {}, + FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr); signals: void groupDataChanged(Group* group); diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 01b7150721..3dbcdaad8b 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -80,21 +80,21 @@ QString PasswordGenerator::generatePassword() const QString password; if (m_flags & CharFromEveryGroup) { - for (int i = 0; i < groups.size(); i++) { - int pos = randomGen()->randomUInt(groups[i].size()); + for (const auto& group : groups) { + int pos = randomGen()->randomUInt(static_cast(group.size())); - password.append(groups[i][pos]); + password.append(group[pos]); } for (int i = groups.size(); i < m_length; i++) { - int pos = randomGen()->randomUInt(passwordChars.size()); + int pos = randomGen()->randomUInt(static_cast(passwordChars.size())); password.append(passwordChars[pos]); } // shuffle chars for (int i = (password.size() - 1); i >= 1; i--) { - int j = randomGen()->randomUInt(i + 1); + int j = randomGen()->randomUInt(static_cast(i + 1)); QChar tmp = password[i]; password[i] = password[j]; @@ -102,7 +102,7 @@ QString PasswordGenerator::generatePassword() const } } else { for (int i = 0; i < m_length; i++) { - int pos = randomGen()->randomUInt(passwordChars.size()); + int pos = randomGen()->randomUInt(static_cast(passwordChars.size())); password.append(passwordChars[pos]); } @@ -111,21 +111,6 @@ QString PasswordGenerator::generatePassword() const return password; } -int PasswordGenerator::getbits() const -{ - const QVector groups = passwordGroups(); - - int bits = 0; - QVector passwordChars; - for (const PasswordGroup& group : groups) { - bits += group.size(); - } - - bits *= m_length; - - return bits; -} - bool PasswordGenerator::isValid() const { if (m_classes == 0) { @@ -138,11 +123,8 @@ bool PasswordGenerator::isValid() const return false; } - if (passwordGroups().size() == 0) { - return false; - } + return !passwordGroups().isEmpty(); - return true; } QVector PasswordGenerator::passwordGroups() const @@ -298,9 +280,9 @@ QVector PasswordGenerator::passwordGroups() const j = group.indexOf(ch); } } - if (group.size() > 0) { + if (!group.isEmpty()) { passwordGroups.replace(i, group); - i++; + ++i; } else { passwordGroups.remove(i); } diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 1d6ac73f67..cb6402d0fc 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -66,7 +66,6 @@ class PasswordGenerator bool isValid() const; QString generatePassword() const; - int getbits() const; static const int DefaultLength = 16; static const char* DefaultExcludedChars; diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 458d429888..60c50b4516 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -18,19 +18,20 @@ */ #include "Tools.h" +#include "core/Config.h" +#include "core/Translator.h" #include #include #include #include #include -#include - #include +#include + #ifdef Q_OS_WIN -#include // for SetSecurityInfo() -#include // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ... +#include // for Sleep() #endif #ifdef Q_OS_UNIX @@ -56,294 +57,161 @@ namespace Tools { - - QString humanReadableFileSize(qint64 bytes, quint32 precision) - { - constexpr auto kibibyte = 1024; - double size = bytes; - - QStringList units = QStringList() << "B" - << "KiB" - << "MiB" - << "GiB"; - int i = 0; - int maxI = units.size() - 1; - - while ((size >= kibibyte) && (i < maxI)) { - size /= kibibyte; - i++; - } - - return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i)); +QString humanReadableFileSize(qint64 bytes, quint32 precision) +{ + constexpr auto kibibyte = 1024; + double size = bytes; + + QStringList units = QStringList() << "B" + << "KiB" + << "MiB" + << "GiB"; + int i = 0; + int maxI = units.size() - 1; + + while ((size >= kibibyte) && (i < maxI)) { + size /= kibibyte; + i++; } - bool hasChild(const QObject* parent, const QObject* child) - { - if (!parent || !child) { - return false; - } + return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i)); +} - const QObjectList children = parent->children(); - for (QObject* c : children) { - if (child == c || hasChild(c, child)) { - return true; - } - } +bool hasChild(const QObject* parent, const QObject* child) +{ + if (!parent || !child) { return false; } - bool readFromDevice(QIODevice* device, QByteArray& data, int size) - { - QByteArray buffer; - buffer.resize(size); - - qint64 readResult = device->read(buffer.data(), size); - if (readResult == -1) { - return false; - } else { - buffer.resize(readResult); - data = buffer; + const QObjectList children = parent->children(); + for (QObject* c : children) { + if (child == c || hasChild(c, child)) { return true; } } + return false; +} - bool readAllFromDevice(QIODevice* device, QByteArray& data) - { - QByteArray result; - qint64 readBytes = 0; - qint64 readResult; - do { - result.resize(result.size() + 16384); - readResult = device->read(result.data() + readBytes, result.size() - readBytes); - if (readResult > 0) { - readBytes += readResult; - } - } while (readResult > 0); +bool readFromDevice(QIODevice* device, QByteArray& data, int size) +{ + QByteArray buffer; + buffer.resize(size); - if (readResult == -1) { - return false; - } else { - result.resize(static_cast(readBytes)); - data = result; - return true; - } + qint64 readResult = device->read(buffer.data(), size); + if (readResult == -1) { + return false; + } else { + buffer.resize(readResult); + data = buffer; + return true; } +} - QString imageReaderFilter() - { - const QList formats = QImageReader::supportedImageFormats(); - QStringList formatsStringList; - - for (const QByteArray& format : formats) { - for (int i = 0; i < format.size(); i++) { - if (!QChar(format.at(i)).isLetterOrNumber()) { - continue; - } - } - - formatsStringList.append("*." + QString::fromLatin1(format).toLower()); +bool readAllFromDevice(QIODevice* device, QByteArray& data) +{ + QByteArray result; + qint64 readBytes = 0; + qint64 readResult; + do { + result.resize(result.size() + 16384); + readResult = device->read(result.data() + readBytes, result.size() - readBytes); + if (readResult > 0) { + readBytes += readResult; } - - return formatsStringList.join(" "); } + while (readResult > 0); - bool isHex(const QByteArray& ba) - { - for (const unsigned char c : ba) { - if (!std::isxdigit(c)) { - return false; - } - } - + if (readResult == -1) { + return false; + } else { + result.resize(static_cast(readBytes)); + data = result; return true; } +} - bool isBase64(const QByteArray& ba) - { - constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)"; - QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2); - - QString base64 = QString::fromLatin1(ba.constData(), ba.size()); - - return regexp.exactMatch(base64); - } - - void sleep(int ms) - { - Q_ASSERT(ms >= 0); +QString imageReaderFilter() +{ + const QList formats = QImageReader::supportedImageFormats(); + QStringList formatsStringList; - if (ms == 0) { - return; + for (const QByteArray& format : formats) { + for (int i = 0; i < format.size(); i++) { + if (!QChar(format.at(i)).isLetterOrNumber()) { + continue; + } } -#ifdef Q_OS_WIN - Sleep(uint(ms)); -#else - timespec ts; - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000 * 1000; - nanosleep(&ts, nullptr); -#endif + formatsStringList.append("*." + QString::fromLatin1(format).toLower()); } - void wait(int ms) - { - Q_ASSERT(ms >= 0); + return formatsStringList.join(" "); +} - if (ms == 0) { - return; - } - - QElapsedTimer timer; - timer.start(); - - if (ms <= 50) { - QCoreApplication::processEvents(QEventLoop::AllEvents, ms); - sleep(qMax(ms - static_cast(timer.elapsed()), 0)); - } else { - int timeLeft; - do { - timeLeft = ms - timer.elapsed(); - if (timeLeft > 0) { - QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft); - sleep(10); - } - } while (!timer.hasExpired(ms)); +bool isHex(const QByteArray& ba) +{ + for (const unsigned char c : ba) { + if (!std::isxdigit(c)) { + return false; } } - void disableCoreDumps() - { - // default to true - // there is no point in printing a warning if this is not implemented on the platform - bool success = true; + return true; +} -#if defined(HAVE_RLIMIT_CORE) - struct rlimit limit; - limit.rlim_cur = 0; - limit.rlim_max = 0; - success = success && (setrlimit(RLIMIT_CORE, &limit) == 0); -#endif +bool isBase64(const QByteArray& ba) +{ + constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)"; + QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2); -#if defined(HAVE_PR_SET_DUMPABLE) - success = success && (prctl(PR_SET_DUMPABLE, 0) == 0); -#endif + QString base64 = QString::fromLatin1(ba.constData(), ba.size()); -// Mac OS X -#ifdef HAVE_PT_DENY_ATTACH - success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0); -#endif + return regexp.exactMatch(base64); +} -#ifdef Q_OS_WIN - success = success && createWindowsDACL(); -#endif +void sleep(int ms) +{ + Q_ASSERT(ms >= 0); - if (!success) { - qWarning("Unable to disable core dumps."); - } + if (ms == 0) { + return; } - void setupSearchPaths() - { #ifdef Q_OS_WIN - // Make sure Windows doesn't load DLLs from the current working directory - SetDllDirectoryA(""); - SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE); + Sleep(uint(ms)); +#else + timespec ts; + ts.tv_sec = ms/1000; + ts.tv_nsec = (ms%1000)*1000*1000; + nanosleep(&ts, nullptr); #endif - } - - // - // This function grants the user associated with the process token minimal access rights and - // denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and - // PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory(). - // We do this using a discretionary access control list (DACL). Effectively this prevents - // crash dumps and disallows other processes from accessing our memory. This works as long - // as you do not have admin privileges, since then you are able to grant yourself the - // SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL. - // - bool createWindowsDACL() - { - bool bSuccess = false; - -#ifdef Q_OS_WIN - // Process token and user - HANDLE hToken = nullptr; - PTOKEN_USER pTokenUser = nullptr; - DWORD cbBufferSize = 0; - - // Access control list - PACL pACL = nullptr; - DWORD cbACL = 0; - - // Open the access token associated with the calling process - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { - goto Cleanup; - } - - // Retrieve the token information in a TOKEN_USER structure - GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize); - - pTokenUser = static_cast(HeapAlloc(GetProcessHeap(), 0, cbBufferSize)); - if (pTokenUser == nullptr) { - goto Cleanup; - } - - if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) { - goto Cleanup; - } - - if (!IsValidSid(pTokenUser->User.Sid)) { - goto Cleanup; - } +} - // Calculate the amount of memory that must be allocated for the DACL - cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid); - - // Create and initialize an ACL - pACL = static_cast(HeapAlloc(GetProcessHeap(), 0, cbACL)); - if (pACL == nullptr) { - goto Cleanup; - } +void wait(int ms) +{ + Q_ASSERT(ms >= 0); - if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) { - goto Cleanup; - } + if (ms == 0) { + return; + } - // Add allowed access control entries, everything else is denied - if (!AddAccessAllowedAce( - pACL, - ACL_REVISION, - SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process - pTokenUser->User.Sid // pointer to the trustee's SID - )) { - goto Cleanup; - } + QElapsedTimer timer; + timer.start(); - // Set discretionary access control list - bSuccess = ERROR_SUCCESS - == SetSecurityInfo(GetCurrentProcess(), // object handle - SE_KERNEL_OBJECT, // type of object - DACL_SECURITY_INFORMATION, // change only the objects DACL - nullptr, - nullptr, // do not change owner or group - pACL, // DACL specified - nullptr // do not change SACL - ); - - Cleanup: - - if (pACL != nullptr) { - HeapFree(GetProcessHeap(), 0, pACL); - } - if (pTokenUser != nullptr) { - HeapFree(GetProcessHeap(), 0, pTokenUser); - } - if (hToken != nullptr) { - CloseHandle(hToken); + if (ms <= 50) { + QCoreApplication::processEvents(QEventLoop::AllEvents, ms); + sleep(qMax(ms - static_cast(timer.elapsed()), 0)); + } else { + int timeLeft; + do { + timeLeft = ms - timer.elapsed(); + if (timeLeft > 0) { + QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft); + sleep(10); + } } -#endif - - return bSuccess; + while (!timer.hasExpired(ms)); } +} } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index 4f75b750bc..aec9373047 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -30,31 +30,27 @@ class QIODevice; namespace Tools { +QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); +bool hasChild(const QObject* parent, const QObject* child); +bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384); +bool readAllFromDevice(QIODevice* device, QByteArray& data); +QString imageReaderFilter(); +bool isHex(const QByteArray& ba); +bool isBase64(const QByteArray& ba); +void sleep(int ms); +void wait(int ms); + +template +RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) +{ + RandomAccessIterator it = std::lower_bound(begin, end, value); - QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); - bool hasChild(const QObject* parent, const QObject* child); - bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384); - bool readAllFromDevice(QIODevice* device, QByteArray& data); - QString imageReaderFilter(); - bool isHex(const QByteArray& ba); - bool isBase64(const QByteArray& ba); - void sleep(int ms); - void wait(int ms); - void disableCoreDumps(); - void setupSearchPaths(); - bool createWindowsDACL(); - - template - RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) - { - RandomAccessIterator it = std::lower_bound(begin, end, value); - - if ((it == end) || (value < *it)) { - return end; - } else { - return it; - } + if ((it == end) || (value < *it)) { + return end; + } else { + return it; } +} } // namespace Tools diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index 12c6bf7910..3c5e4c2840 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -98,13 +98,6 @@ void CryptoHash::setKey(const QByteArray& data) Q_ASSERT(error == 0); } -void CryptoHash::reset() -{ - Q_D(CryptoHash); - - gcry_md_reset(d->ctx); -} - QByteArray CryptoHash::result() const { Q_D(const CryptoHash); diff --git a/src/crypto/CryptoHash.h b/src/crypto/CryptoHash.h index bd312121ab..02f90eb4de 100644 --- a/src/crypto/CryptoHash.h +++ b/src/crypto/CryptoHash.h @@ -34,7 +34,6 @@ class CryptoHash explicit CryptoHash(Algorithm algo, bool hmac = false); ~CryptoHash(); void addData(const QByteArray& data); - void reset(); QByteArray result() const; void setKey(const QByteArray& data); diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 0467ad7c2f..828d3a998a 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -94,7 +94,7 @@ QString SymmetricCipher::errorString() const SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& cipher) { - if (cipher == KeePass2::CIPHER_AES) { + if (cipher == KeePass2::CIPHER_AES256) { return Aes256; } else if (cipher == KeePass2::CIPHER_CHACHA20) { return ChaCha20; @@ -109,15 +109,17 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& ciphe QUuid SymmetricCipher::algorithmToCipher(Algorithm algo) { switch (algo) { + case Aes128: + return KeePass2::CIPHER_AES128; case Aes256: - return KeePass2::CIPHER_AES; + return KeePass2::CIPHER_AES256; case ChaCha20: return KeePass2::CIPHER_CHACHA20; case Twofish: return KeePass2::CIPHER_TWOFISH; default: qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo); - return QUuid(); + return {}; } } diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index c7a5e6a071..e3bc88cbf0 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -185,8 +185,6 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data) bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds) { - // TODO: check block size - gcry_error_t error; char* rawData = data.data(); diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index aeacaad3de..0114a0b767 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -110,14 +110,6 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, return nullptr; } - QBuffer buffer; - if (saveXml()) { - m_xmlData = xmlDevice->readAll(); - buffer.setBuffer(&m_xmlData); - buffer.open(QIODevice::ReadOnly); - xmlDevice = &buffer; - } - Q_ASSERT(xmlDevice); KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1); diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 7b94d34f81..5a024a254a 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -124,14 +124,6 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, return nullptr; } - QBuffer buffer; - if (saveXml()) { - m_xmlData = xmlDevice->readAll(); - buffer.setBuffer(&m_xmlData); - buffer.open(QIODevice::ReadOnly); - xmlDevice = &buffer; - } - Q_ASSERT(xmlDevice); KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool()); diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp index 5e14b8a9f1..13a792fd5d 100644 --- a/src/format/KdbxReader.cpp +++ b/src/format/KdbxReader.cpp @@ -1,3 +1,5 @@ +#include + /* * Copyright (C) 2018 KeePassXC Team * @@ -18,6 +20,9 @@ #include "KdbxReader.h" #include "core/Database.h" #include "core/Endian.h" +#include "format/KdbxXmlWriter.h" + +#include #define UUID_LENGTH 16 @@ -92,7 +97,14 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer m_db; QPair m_kdbxSignature; diff --git a/src/format/KdbxXmlReader.cpp b/src/format/KdbxXmlReader.cpp index 76fa032217..c9e5c31af9 100644 --- a/src/format/KdbxXmlReader.cpp +++ b/src/format/KdbxXmlReader.cpp @@ -82,7 +82,6 @@ Database* KdbxXmlReader::readDatabase(QIODevice* device) * @param db database to read into * @param randomStream random stream to use for decryption */ -#include "QDebug" void KdbxXmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_error = false; diff --git a/src/format/KdbxXmlWriter.cpp b/src/format/KdbxXmlWriter.cpp index 5ad1e34aea..c26d316dc3 100644 --- a/src/format/KdbxXmlWriter.cpp +++ b/src/format/KdbxXmlWriter.cpp @@ -358,10 +358,10 @@ void KdbxXmlWriter::writeEntry(const Entry* entry) bool protect = (((key == "Title") && m_meta->protectTitle()) || ((key == "UserName") && m_meta->protectUsername()) - || ((key == "Password") && m_meta->protectPassword()) - || ((key == "URL") && m_meta->protectUrl()) - || ((key == "Notes") && m_meta->protectNotes()) - || entry->attributes()->isProtected(key)); + || ((key == "Password") && m_meta->protectPassword()) + || ((key == "URL") && m_meta->protectUrl()) + || ((key == "Notes") && m_meta->protectNotes()) + || entry->attributes()->isProtected(key)); writeString("Key", key); @@ -369,7 +369,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry) QString value; if (protect) { - if (m_randomStream) { + if (!m_innerStreamProtectionDisabled && m_randomStream) { m_xml.writeAttribute("Protected", "True"); bool ok; QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok); @@ -596,3 +596,24 @@ void KdbxXmlWriter::raiseError(const QString& errorMessage) m_error = true; m_errorStr = errorMessage; } + +/** + * Disable inner stream protection and write protected fields + * in plaintext instead. This is useful for plaintext XML exports + * where the inner stream key is not available. + * + * @param disable true to disable protection + */ +void KdbxXmlWriter::disableInnerStreamProtection(bool disable) +{ + m_innerStreamProtectionDisabled = disable; +} + +/** + * @return true if inner stream protection is disabled and protected + * fields will be saved in plaintext + */ +bool KdbxXmlWriter::innerStreamProtectionDisabled() const +{ + return m_innerStreamProtectionDisabled; +} diff --git a/src/format/KdbxXmlWriter.h b/src/format/KdbxXmlWriter.h index 51a8034979..2f7215b46a 100644 --- a/src/format/KdbxXmlWriter.h +++ b/src/format/KdbxXmlWriter.h @@ -41,6 +41,8 @@ class KdbxXmlWriter KeePass2RandomStream* randomStream = nullptr, const QByteArray& headerHash = QByteArray()); void writeDatabase(const QString& filename, Database* db); + void disableInnerStreamProtection(bool disable); + bool innerStreamProtectionDisabled() const; bool hasError(); QString errorString(); @@ -81,6 +83,8 @@ class KdbxXmlWriter const quint32 m_kdbxVersion; + bool m_innerStreamProtectionDisabled = false; + QXmlStreamWriter m_xml; QPointer m_db; QPointer m_meta; diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 5aad1f7f21..9c07144844 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -23,7 +23,8 @@ #define UUID_LENGTH 16 -const QUuid KeePass2::CIPHER_AES = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff"); +const QUuid KeePass2::CIPHER_AES128 = QUuid("61ab05a1-9464-41c3-8d74-3a563df8dd35"); +const QUuid KeePass2::CIPHER_AES256 = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff"); const QUuid KeePass2::CIPHER_TWOFISH = QUuid("ad68f29f-576f-4bb9-a36a-d47af965346c"); const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb59a"); @@ -47,7 +48,7 @@ const QString KeePass2::KDFPARAM_ARGON2_SECRET("K"); const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A"); const QList> KeePass2::CIPHERS{ - qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), + qMakePair(KeePass2::CIPHER_AES256, QObject::tr("AES: 256-bit")), qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) }; diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index a8e97c5bdf..02fe635ca0 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -46,7 +46,8 @@ namespace KeePass2 const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; -extern const QUuid CIPHER_AES; +extern const QUuid CIPHER_AES128; +extern const QUuid CIPHER_AES256; extern const QUuid CIPHER_TWOFISH; extern const QUuid CIPHER_CHACHA20; diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 2a6d611182..8cdb8ff430 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -93,7 +93,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointersetSaveXml(m_saveXml); - return m_reader->readDatabase(device, key, keepDatabase); + return m_reader->readDatabase(device, std::move(key), keepDatabase); } bool KeePass2Reader::hasError() const diff --git a/src/gui/Application.h b/src/gui/Application.h index a6c6fdf905..3fdd8af90e 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -22,8 +22,8 @@ #include #include -class QLockFile; +class QLockFile; class QSocketNotifier; class Application : public QApplication diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 896703eb68..399c9ec82a 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -210,7 +210,7 @@ private slots: void setIconFromParent(); void replaceDatabase(Database* db); - Database* m_db; + QPointer m_db; QWidget* m_mainWidget; EditEntryWidget* m_editEntryWidget; EditEntryWidget* m_historyEditEntryWidget; diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp index 2a3cf7cbbb..63a1ccef8f 100644 --- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp +++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp @@ -81,7 +81,7 @@ void DatabaseSettingsWidgetEncryption::initialize() } if (!m_db->key()) { m_db->setKey(QSharedPointer::create()); - m_db->setCipher(KeePass2::CIPHER_AES); + m_db->setCipher(KeePass2::CIPHER_AES256); isDirty = true; } diff --git a/src/main.cpp b/src/main.cpp index 687988762c..7b7ac5e414 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,19 +16,18 @@ * along with this program. If not, see . */ -#include #include #include +#include #include "config-keepassx.h" -#include "core/Config.h" +#include "core/Bootstrap.h" #include "core/Tools.h" -#include "core/Translator.h" +#include "core/Config.h" #include "crypto/Crypto.h" #include "gui/Application.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" - #include "cli/Utils.h" #if defined(WITH_ASAN) && defined(WITH_LSAN) @@ -45,55 +44,29 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) #endif #endif -static inline void earlyQNetworkAccessManagerWorkaround() -{ - // When QNetworkAccessManager is instantiated it regularly starts polling - // all network interfaces to see if anything changes and if so, what. This - // creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >= - // when on a wifi connection. - // So here we disable it for lack of better measure. - // This will also cause this message: QObject::startTimer: Timers cannot - // have negative intervals - // For more info see: - // - https://bugreports.qt.io/browse/QTBUG-40332 - // - https://bugreports.qt.io/browse/QTBUG-46015 - qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); -} - int main(int argc, char** argv) { -#ifdef QT_NO_DEBUG - Tools::disableCoreDumps(); -#endif - Tools::setupSearchPaths(); - - earlyQNetworkAccessManagerWorkaround(); - Application app(argc, argv); Application::setApplicationName("keepassxc"); Application::setApplicationVersion(KEEPASSX_VERSION); // don't set organizationName as that changes the return value of // QStandardPaths::writableLocation(QDesktopServices::DataLocation) + Bootstrap::bootstrapApplication(); QCommandLineParser parser; - parser.setApplicationDescription( - QCoreApplication::translate("main", "KeePassXC - cross-platform password manager")); - parser.addPositionalArgument( - "filename", - QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"), - "[filename(s)]"); + parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC - cross-platform password manager")); + parser.addPositionalArgument("filename", + QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"), "[filename(s)]"); QCommandLineOption configOption( "config", QCoreApplication::translate("main", "path to a custom config file"), "config"); QCommandLineOption keyfileOption( "keyfile", QCoreApplication::translate("main", "key file of the database"), "keyfile"); QCommandLineOption pwstdinOption("pw-stdin", - QCoreApplication::translate("main", "read password of the database from stdin")); + QCoreApplication::translate("main", "read password of the database from stdin")); // This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method - QCommandLineOption parentWindowOption(QStringList() << "pw" - << "parent-window", - QCoreApplication::translate("main", "Parent window handle"), - "handle"); + QCommandLineOption parentWindowOption( + QStringList() << "pw" << "parent-window", QCoreApplication::translate("main", "Parent window handle"), "handle"); parser.addHelpOption(); QCommandLineOption versionOption = parser.addVersionOption(); @@ -109,9 +82,7 @@ int main(int argc, char** argv) if (!fileNames.isEmpty()) { app.sendFileNamesToRunningInstance(fileNames); } - qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.") - .toUtf8() - .constData(); + qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData(); return 0; } @@ -129,46 +100,14 @@ int main(int argc, char** argv) Config::createConfigFromFile(parser.value(configOption)); } - Translator::installTranslators(); - -#ifdef Q_OS_MAC - // Don't show menu icons on OSX - QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); -#endif - MainWindow mainWindow; app.setMainWindow(&mainWindow); - QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection); - // start minimized if configured - bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool(); - bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool(); -#ifndef Q_OS_LINUX - if (minimizeOnStartup) { -#else - // On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at - // the same time (which would happen if both minimize on startup and minimize to tray are set) - // since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough. - if (minimizeOnStartup && !minimizeToTray) { -#endif - mainWindow.setWindowState(Qt::WindowMinimized); - } - if (!(minimizeOnStartup && minimizeToTray)) { - mainWindow.show(); - } - - if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { - const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList(); - for (const QString& filename : fileNames) { - if (!filename.isEmpty() && QFile::exists(filename)) { - mainWindow.openDatabase(filename); - } - } - } + Bootstrap::restoreMainWindowState(mainWindow); const bool pwstdin = parser.isSet(pwstdinOption); for (const QString& filename : fileNames) { @@ -187,7 +126,7 @@ int main(int argc, char** argv) } } - int exitCode = app.exec(); + int exitCode = Application::exec(); #if defined(WITH_ASAN) && defined(WITH_LSAN) // do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 73262bae07..562b45e4d3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -163,7 +163,10 @@ add_unit_test(NAME testentry SOURCES TestEntry.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testmerge SOURCES TestMerge.cpp - LIBS testsupport ${TEST_LIBRARIES}) + LIBS testsupport ${TEST_LIBRARIES}) + +add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp + LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testtotp SOURCES TestTotp.cpp LIBS ${TEST_LIBRARIES}) @@ -180,7 +183,7 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp +add_unit_test(NAME testcsveporter SOURCES TestCsvExporter.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testykchallengeresponsekey @@ -193,6 +196,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp add_unit_test(NAME testtools SOURCES TestTools.cpp LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) + # CLI clip tests need X environment on Linux + add_unit_test(NAME testcli SOURCES TestCli.cpp + LIBS testsupport cli ${TEST_LIBRARIES}) + add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp new file mode 100644 index 0000000000..02bc0ba3f5 --- /dev/null +++ b/tests/TestCli.cpp @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2018 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 "TestCli.h" +#include "config-keepassx-tests.h" +#include "core/Global.h" +#include "core/Config.h" +#include "core/Bootstrap.h" +#include "core/Tools.h" +#include "core/PasswordGenerator.h" +#include "crypto/Crypto.h" +#include "format/KeePass2.h" +#include "format/Kdbx3Reader.h" +#include "format/Kdbx4Reader.h" +#include "format/Kdbx4Writer.h" +#include "format/Kdbx3Writer.h" +#include "format/KdbxXmlReader.h" + +#include "cli/Command.h" +#include "cli/Utils.h" +#include "cli/Add.h" +#include "cli/Clip.h" +#include "cli/Diceware.h" +#include "cli/Edit.h" +#include "cli/Estimate.h" +#include "cli/Extract.h" +#include "cli/Generate.h" +#include "cli/List.h" +#include "cli/Locate.h" +#include "cli/Merge.h" +#include "cli/Remove.h" +#include "cli/Show.h" + +#include +#include +#include +#include +#include + +#include + +QTEST_MAIN(TestCli) + +void TestCli::initTestCase() +{ + QVERIFY(Crypto::init()); + + Config::createTempFileInstance(); + Bootstrap::bootstrapApplication(); + + // Load the NewDatabase.kdbx file into temporary storage + QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx")); + QVERIFY(sourceDbFile.open(QIODevice::ReadOnly)); + QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData)); + sourceDbFile.close(); +} + +void TestCli::init() +{ + m_dbFile.reset(new QTemporaryFile()); + m_dbFile->open(); + m_dbFile->write(m_dbData); + m_dbFile->flush(); + + m_stdinFile.reset(new QTemporaryFile()); + m_stdinFile->open(); + m_stdinHandle = fdopen(m_stdinFile->handle(), "r+"); + Utils::STDIN = m_stdinHandle; + + m_stdoutFile.reset(new QTemporaryFile()); + m_stdoutFile->open(); + m_stdoutHandle = fdopen(m_stdoutFile->handle(), "r+"); + Utils::STDOUT = m_stdoutHandle; + + m_stderrFile.reset(new QTemporaryFile()); + m_stderrFile->open(); + m_stderrHandle = fdopen(m_stderrFile->handle(), "r+"); + Utils::STDERR = m_stderrHandle; +} + +void TestCli::cleanup() +{ + m_dbFile.reset(); + + m_stdinFile.reset(); + m_stdinHandle = stdin; + Utils::STDIN = stdin; + + m_stdoutFile.reset(); + Utils::STDOUT = stdout; + m_stdoutHandle = stdout; + + m_stderrFile.reset(); + m_stderrHandle = stderr; + Utils::STDERR = stderr; +} + +void TestCli::cleanupTestCase() +{ +} + +QSharedPointer TestCli::readTestDatabase() const +{ + Utils::setNextPassword("a"); + auto db = QSharedPointer(Database::unlockFromStdin(m_dbFile->fileName(), "", m_stdoutHandle)); + m_stdoutFile->seek(ftell(m_stdoutHandle)); // re-synchronize handles + return db; +} + +void TestCli::testCommand() +{ + QCOMPARE(Command::getCommands().size(), 12); + QVERIFY(Command::getCommand("add")); + QVERIFY(Command::getCommand("clip")); + QVERIFY(Command::getCommand("diceware")); + QVERIFY(Command::getCommand("edit")); + QVERIFY(Command::getCommand("estimate")); + QVERIFY(Command::getCommand("extract")); + QVERIFY(Command::getCommand("generate")); + QVERIFY(Command::getCommand("locate")); + QVERIFY(Command::getCommand("ls")); + QVERIFY(Command::getCommand("merge")); + QVERIFY(Command::getCommand("rm")); + QVERIFY(Command::getCommand("show")); + QVERIFY(!Command::getCommand("doesnotexist")); +} + +void TestCli::testAdd() +{ + Add addCmd; + QVERIFY(!addCmd.name.isEmpty()); + QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name)); + + Utils::setNextPassword("a"); + addCmd.execute({"add", "-u", "newuser", "--url", "https://example.com/", "-g", "-l", "20", m_dbFile->fileName(), "/newuser-entry"}); + + auto db = readTestDatabase(); + auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://example.com/")); + QCOMPARE(entry->password().size(), 20); + + Utils::setNextPassword("a"); + Utils::setNextPassword("newpassword"); + addCmd.execute({"add", "-u", "newuser2", "--url", "https://example.net/", "-g", "-l", "20", "-p", m_dbFile->fileName(), "/newuser-entry2"}); + + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/newuser-entry2"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser2")); + QCOMPARE(entry->url(), QString("https://example.net/")); + QCOMPARE(entry->password(), QString("newpassword")); +} + +void TestCli::testClip() +{ + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->clear(); + + Clip clipCmd; + QVERIFY(!clipCmd.name.isEmpty()); + QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name)); + + Utils::setNextPassword("a"); + clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"}); + + m_stderrFile->reset(); + QString errorOutput(m_stderrFile->readAll()); + + if (errorOutput.contains("Unable to start program") + || errorOutput.contains("No program defined for clipboard manipulation")) { + QSKIP("Clip test skipped due to missing clipboard tool"); + } + + QCOMPARE(clipboard->text(), QString("Password")); + + Utils::setNextPassword("a"); + QFuture future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"}); + + QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500); + QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500); + + future.waitForFinished(); +} + +void TestCli::testDiceware() +{ + Diceware dicewareCmd; + QVERIFY(!dicewareCmd.name.isEmpty()); + QVERIFY(dicewareCmd.getDescriptionLine().contains(dicewareCmd.name)); + + dicewareCmd.execute({"diceware"}); + m_stdoutFile->reset(); + QString passphrase(m_stdoutFile->readLine()); + QVERIFY(!passphrase.isEmpty()); + + dicewareCmd.execute({"diceware", "-W", "2"}); + m_stdoutFile->seek(passphrase.toLatin1().size()); + passphrase = m_stdoutFile->readLine(); + QCOMPARE(passphrase.split(" ").size(), 2); + + auto pos = m_stdoutFile->pos(); + dicewareCmd.execute({"diceware", "-W", "10"}); + m_stdoutFile->seek(pos); + passphrase = m_stdoutFile->readLine(); + QCOMPARE(passphrase.split(" ").size(), 10); + + QTemporaryFile wordFile; + wordFile.open(); + for (int i = 0; i < 4500; ++i) { + wordFile.write(QString("word" + QString::number(i) + "\n").toLatin1()); + } + wordFile.close(); + + pos = m_stdoutFile->pos(); + dicewareCmd.execute({"diceware", "-W", "11", "-w", wordFile.fileName()}); + m_stdoutFile->seek(pos); + passphrase = m_stdoutFile->readLine(); + const auto words = passphrase.split(" "); + QCOMPARE(words.size(), 11); + QRegularExpression regex("^word\\d+$"); + for (const auto& word: words) { + QVERIFY2(regex.match(word).hasMatch(), qPrintable("Word " + word + " was not on the word list")); + } +} + +void TestCli::testEdit() +{ + Edit editCmd; + QVERIFY(!editCmd.name.isEmpty()); + QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name)); + + Utils::setNextPassword("a"); + editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"}); + + auto db = readTestDatabase(); + auto* entry = db->rootGroup()->findEntryByPath("/newtitle"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); + QCOMPARE(entry->password(), QString("Password")); + + Utils::setNextPassword("a"); + editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newtitle"}); + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/newtitle"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); + QVERIFY(!entry->password().isEmpty()); + QVERIFY(entry->password() != QString("Password")); + + Utils::setNextPassword("a"); + editCmd.execute({"edit", "-g", "-l", "34", "-t", "yet another title", m_dbFile->fileName(), "/newtitle"}); + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/yet another title"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); + QVERIFY(entry->password() != QString("Password")); + QCOMPARE(entry->password().size(), 34); + + Utils::setNextPassword("a"); + Utils::setNextPassword("newpassword"); + editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/yet another title"}); + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/yet another title"); + QVERIFY(entry); + QCOMPARE(entry->password(), QString("newpassword")); +} + +void TestCli::testEstimate_data() +{ + QTest::addColumn("input"); + QTest::addColumn("length"); + QTest::addColumn("entropy"); + QTest::addColumn("log10"); + QTest::addColumn("searchStrings"); + + QTest::newRow("Dictionary") + << "password" << "8" << "1.0" << "0.3" + << QStringList{"Type: Dictionary", "\tpassword"}; + + QTest::newRow("Spatial") + << "zxcv" << "4" << "10.3" << "3.1" + << QStringList{"Type: Spatial", "\tzxcv"}; + + QTest::newRow("Spatial(Rep)") + << "sdfgsdfg" << "8" << "11.3" << "3.4" + << QStringList{"Type: Spatial(Rep)", "\tsdfgsdfg"}; + + QTest::newRow("Dictionary / Sequence") + << "password123" << "11" << "4.5" << "1.3" + << QStringList{"Type: Dictionary", "Type: Sequence", "\tpassword", "\t123"}; + + QTest::newRow("Dict+Leet") + << "p455w0rd" << "8" << "2.5" << "0.7" + << QStringList{"Type: Dict+Leet", "\tp455w0rd"}; + + QTest::newRow("Dictionary(Rep)") + << "hellohello" << "10" << "7.3" << "2.2" + << QStringList{"Type: Dictionary(Rep)", "\thellohello"}; + + QTest::newRow("Sequence(Rep) / Dictionary") + << "456456foobar" << "12" << "16.7" << "5.0" + << QStringList{"Type: Sequence(Rep)", "Type: Dictionary", "\t456456", "\tfoobar"}; + + QTest::newRow("Bruteforce(Rep) / Bruteforce") + << "xzxzy" << "5" << "16.1" << "4.8" + << QStringList{"Type: Bruteforce(Rep)", "Type: Bruteforce", "\txzxz", "\ty"}; + + QTest::newRow("Dictionary / Date(Rep)") + << "pass20182018" << "12" << "15.1" << "4.56" + << QStringList{"Type: Dictionary", "Type: Date(Rep)", "\tpass", "\t20182018"}; + + QTest::newRow("Dictionary / Date / Bruteforce") + << "mypass2018-2" << "12" << "32.9" << "9.9" + << QStringList{"Type: Dictionary", "Type: Date", "Type: Bruteforce", "\tmypass", "\t2018", "\t-2"}; + + QTest::newRow("Strong Password") + << "E*!%.Qw{t.X,&bafw)\"Q!ah$%;U/" << "28" << "165.7" << "49.8" + << QStringList{"Type: Bruteforce", "\tE*"}; + + // TODO: detect passphrases and adjust entropy calculation accordingly (issue #2347) + QTest::newRow("Strong Passphrase") + << "squint wooing resupply dangle isolation axis headsman" << "53" << "151.2" << "45.5" + << QStringList{"Type: Dictionary", "Type: Bruteforce", "Multi-word extra bits 22.0", "\tsquint", "\t ", "\twooing"}; +} + +void TestCli::testEstimate() +{ + QFETCH(QString, input); + QFETCH(QString, length); + QFETCH(QString, entropy); + QFETCH(QString, log10); + QFETCH(QStringList, searchStrings); + + Estimate estimateCmd; + QVERIFY(!estimateCmd.name.isEmpty()); + QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name)); + + QTextStream in(m_stdinFile.data()); + QTextStream out(m_stdoutFile.data()); + + in << input << endl; + auto inEnd = in.pos(); + in.seek(0); + estimateCmd.execute({"estimate"}); + auto outEnd = out.pos(); + out.seek(0); + auto result = out.readAll(); + QVERIFY(result.startsWith("Length " + length)); + QVERIFY(result.contains("Entropy " + entropy)); + QVERIFY(result.contains("Log10 " + log10)); + + // seek to end of stream + in.seek(inEnd); + out.seek(outEnd); + + in << input << endl; + in.seek(inEnd); + estimateCmd.execute({"estimate", "-a"}); + out.seek(outEnd); + result = out.readAll(); + QVERIFY(result.startsWith("Length " + length)); + QVERIFY(result.contains("Entropy " + entropy)); + QVERIFY(result.contains("Log10 " + log10)); + for (const auto& string: asConst(searchStrings)) { + QVERIFY2(result.contains(string), qPrintable("String " + string + " missing")); + } +} + +void TestCli::testExtract() +{ + Extract extractCmd; + QVERIFY(!extractCmd.name.isEmpty()); + QVERIFY(extractCmd.getDescriptionLine().contains(extractCmd.name)); + + Utils::setNextPassword("a"); + extractCmd.execute({"extract", m_dbFile->fileName()}); + + m_stdoutFile->seek(0); + m_stdoutFile->readLine(); // skip prompt line + + KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1); + QScopedPointer db(new Database()); + reader.readDatabase(m_stdoutFile.data(), db.data()); + QVERIFY(!reader.hasError()); + QVERIFY(db.data()); + auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry"); + QVERIFY(entry); + QCOMPARE(entry->password(), QString("Password")); +} + +void TestCli::testGenerate_data() +{ + QTest::addColumn("parameters"); + QTest::addColumn("pattern"); + + QTest::newRow("default") << QStringList{"generate"} << "^[^\r\n]+$"; + QTest::newRow("length") << QStringList{"generate", "-L", "13"} << "^.{13}$"; + QTest::newRow("lowercase") << QStringList{"generate", "-L", "14", "-l"} << "^[a-z]{14}$"; + QTest::newRow("uppercase") << QStringList{"generate", "-L", "15", "-u"} << "^[A-Z]{15}$"; + QTest::newRow("numbers")<< QStringList{"generate", "-L", "16", "-n"} << "^[0-9]{16}$"; + QTest::newRow("special") + << QStringList{"generate", "-L", "200", "-s"} + << R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!+-<=>?#$%&^`@~]{200}$)"; + QTest::newRow("special (exclude)") + << QStringList{"generate", "-L", "200", "-s" , "-x", "+.?@&"} + << R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!-<=>#$%^`~]{200}$)"; + QTest::newRow("extended") + << QStringList{"generate", "-L", "50", "-e"} + << R"(^[^a-zA-Z0-9\(\)\[\]\{\}\.\-\*\|\\,:;"'\/\_!+-<=>?#$%&^`@~]{50}$)"; + QTest::newRow("numbers + lowercase + uppercase") + << QStringList{"generate", "-L", "16", "-n", "-u", "-l"} + << "^[0-9a-zA-Z]{16}$"; + QTest::newRow("numbers + lowercase + uppercase (exclude)") + << QStringList{"generate", "-L", "500", "-n", "-u", "-l", "-x", "abcdefg0123@"} + << "^[^abcdefg0123@]{500}$"; + QTest::newRow("numbers + lowercase + uppercase (exclude similar)") + << QStringList{"generate", "-L", "200", "-n", "-u", "-l", "--exclude-similar"} + << "^[^l1IO0]{200}$"; + QTest::newRow("uppercase + lowercase (every)") + << QStringList{"generate", "-L", "2", "-u", "-l", "--every-group"} + << "^[a-z][A-Z]|[A-Z][a-z]$"; + QTest::newRow("numbers + lowercase (every)") + << QStringList{"generate", "-L", "2", "-n", "-l", "--every-group"} + << "^[a-z][0-9]|[0-9][a-z]$"; +} + +void TestCli::testGenerate() +{ + QFETCH(QStringList, parameters); + QFETCH(QString, pattern); + + Generate generateCmd; + QVERIFY(!generateCmd.name.isEmpty()); + QVERIFY(generateCmd.getDescriptionLine().contains(generateCmd.name)); + + qint64 pos = 0; + // run multiple times to make accidental passes unlikely + for (int i = 0; i < 10; ++i) { + generateCmd.execute(parameters); + m_stdoutFile->seek(pos); + QRegularExpression regex(pattern); + QString password = QString::fromUtf8(m_stdoutFile->readLine()); + pos = m_stdoutFile->pos(); + QVERIFY2(regex.match(password).hasMatch(), qPrintable("Password " + password + " does not match pattern " + pattern)); + } +} + +void TestCli::testList() +{ + List listCmd; + QVERIFY(!listCmd.name.isEmpty()); + QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name)); + + Utils::setNextPassword("a"); + listCmd.execute({"ls", m_dbFile->fileName()}); + m_stdoutFile->reset(); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "General/\n" + "Windows/\n" + "Network/\n" + "Internet/\n" + "eMail/\n" + "Homebanking/\n")); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + listCmd.execute({"ls", "-R", m_dbFile->fileName()}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "General/\n" + " [empty]\n" + "Windows/\n" + " [empty]\n" + "Network/\n" + " [empty]\n" + "Internet/\n" + " [empty]\n" + "eMail/\n" + " [empty]\n" + "Homebanking/\n" + " [empty]\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + listCmd.execute({"ls", m_dbFile->fileName(), "/General/"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("[empty]\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + listCmd.execute({"ls", m_dbFile->fileName(), "/DoesNotExist/"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("Cannot find group /DoesNotExist/.\n")); +} + +void TestCli::testLocate() +{ + Locate locateCmd; + QVERIFY(!locateCmd.name.isEmpty()); + QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name)); + + Utils::setNextPassword("a"); + locateCmd.execute({"locate", m_dbFile->fileName(), "Sample"}); + m_stdoutFile->reset(); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n")); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + locateCmd.execute({"locate", m_dbFile->fileName(), "Does Not Exist"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("No results for that search term.\n")); + + // write a modified database + auto db = readTestDatabase(); + QVERIFY(db); + auto* group = db->rootGroup()->findGroupByPath("/General/"); + QVERIFY(group); + auto* entry = new Entry(); + entry->setUuid(QUuid::createUuid()); + entry->setTitle("New Entry"); + group->addEntry(entry); + QTemporaryFile tmpFile; + tmpFile.open(); + Kdbx4Writer writer; + writer.writeDatabase(&tmpFile, db.data()); + tmpFile.close(); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + locateCmd.execute({"locate", tmpFile.fileName(), "New"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("/General/New Entry\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + locateCmd.execute({"locate", tmpFile.fileName(), "Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n/General/New Entry\n")); +} + +void TestCli::testMerge() +{ + Merge mergeCmd; + QVERIFY(!mergeCmd.name.isEmpty()); + QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name)); + + Kdbx4Writer writer; + Kdbx4Reader reader; + + // load test database and save a copy + auto db = readTestDatabase(); + QVERIFY(db); + QTemporaryFile targetFile1; + targetFile1.open(); + writer.writeDatabase(&targetFile1, db.data()); + targetFile1.close(); + + // save another copy with a different password + QTemporaryFile targetFile2; + targetFile2.open(); + auto oldKey = db->key(); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("b")); + db->setKey(key); + writer.writeDatabase(&targetFile2, db.data()); + targetFile2.close(); + db->setKey(oldKey); + + // then add a new entry to the in-memory database and save another copy + auto* entry = new Entry(); + entry->setUuid(QUuid::createUuid()); + entry->setTitle("Some Website"); + entry->setPassword("secretsecretsecret"); + auto* group = db->rootGroup()->findGroupByPath("/Internet/"); + QVERIFY(group); + group->addEntry(entry); + QTemporaryFile sourceFile; + sourceFile.open(); + writer.writeDatabase(&sourceFile, db.data()); + sourceFile.close(); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n")); + + QFile readBack(targetFile1.fileName()); + readBack.open(QIODevice::ReadOnly); + QScopedPointer mergedDb(reader.readDatabase(&readBack, oldKey)); + readBack.close(); + QVERIFY(mergedDb); + auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); + QVERIFY(entry1); + QCOMPARE(entry1->title(), QString("Some Website")); + QCOMPARE(entry1->password(), QString("secretsecretsecret")); + + // try again with different passwords for both files + pos = m_stdoutFile->pos(); + Utils::setNextPassword("b"); + Utils::setNextPassword("a"); + mergeCmd.execute({"merge", targetFile2.fileName(), sourceFile.fileName()}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); + m_stdoutFile->readLine(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n")); + + readBack.setFileName(targetFile2.fileName()); + readBack.open(QIODevice::ReadOnly); + mergedDb.reset(reader.readDatabase(&readBack, key)); + readBack.close(); + QVERIFY(mergedDb); + entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); + QVERIFY(entry1); + QCOMPARE(entry1->title(), QString("Some Website")); + QCOMPARE(entry1->password(), QString("secretsecretsecret")); +} + +void TestCli::testRemove() +{ + Remove removeCmd; + QVERIFY(!removeCmd.name.isEmpty()); + QVERIFY(removeCmd.getDescriptionLine().contains(removeCmd.name)); + + Kdbx3Reader reader; + Kdbx3Writer writer; + + // load test database and save a copy with disabled recycle bin + auto db = readTestDatabase(); + QVERIFY(db); + QTemporaryFile fileCopy; + fileCopy.open(); + db->metadata()->setRecycleBinEnabled(false); + writer.writeDatabase(&fileCopy, db.data()); + fileCopy.close(); + + qint64 pos = m_stdoutFile->pos(); + + // delete entry and verify + Utils::setNextPassword("a"); + removeCmd.execute({"rm", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully recycled entry Sample Entry.\n")); + + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("a")); + QFile readBack(m_dbFile->fileName()); + readBack.open(QIODevice::ReadOnly); + QScopedPointer readBackDb(reader.readDatabase(&readBack, key)); + readBack.close(); + QVERIFY(readBackDb); + QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry")); + + pos = m_stdoutFile->pos(); + + // try again, this time without recycle bin + Utils::setNextPassword("a"); + removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully deleted entry Sample Entry.\n")); + + readBack.setFileName(fileCopy.fileName()); + readBack.open(QIODevice::ReadOnly); + readBackDb.reset(reader.readDatabase(&readBack, key)); + readBack.close(); + QVERIFY(readBackDb); + QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry")); + + pos = m_stdoutFile->pos(); + + // finally, try deleting a non-existent entry + Utils::setNextPassword("a"); + removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n")); +} + +void TestCli::testShow() +{ + Show showCmd; + QVERIFY(!showCmd.name.isEmpty()); + QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name)); + + Utils::setNextPassword("a"); + showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->reset(); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Title: Sample Entry\n" + "UserName: User Name\n" + "Password: Password\n" + "URL: http://www.somesite.com/\n" + "Notes: Notes\n")); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + showCmd.execute({"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "http://www.somesite.com/\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + showCmd.execute({"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("ERROR: unknown attribute DoesNotExist.\n")); +} diff --git a/tests/TestCli.h b/tests/TestCli.h new file mode 100644 index 0000000000..532d84a793 --- /dev/null +++ b/tests/TestCli.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 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_TESTCLI_H +#define KEEPASSXC_TESTCLI_H + +#include "core/Database.h" + +#include +#include +#include +#include +#include + +class TestCli : public QObject +{ + Q_OBJECT + +private: + QSharedPointer readTestDatabase() const; + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void testCommand(); + void testAdd(); + void testClip(); + void testDiceware(); + void testEdit(); + void testEstimate_data(); + void testEstimate(); + void testExtract(); + void testGenerate_data(); + void testGenerate(); + void testList(); + void testLocate(); + void testMerge(); + void testRemove(); + void testShow(); + +private: + QByteArray m_dbData; + QScopedPointer m_dbFile; + QScopedPointer m_stdoutFile; + QScopedPointer m_stderrFile; + QScopedPointer m_stdinFile; + FILE* m_stdoutHandle = stdout; + FILE* m_stderrHandle = stderr; + FILE* m_stdinHandle = stdin; +}; + +#endif //KEEPASSXC_TESTCLI_H diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp index 72c2d4c834..297cde284e 100644 --- a/tests/TestKdbx4.cpp +++ b/tests/TestKdbx4.cpp @@ -199,12 +199,12 @@ void TestKdbx4::testFormat400Upgrade_data() auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK; auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; - QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << false << kdbx4; - QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << false << kdbx4; - QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << false << kdbx3; - QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << true << kdbx4; - QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << true << kdbx4; - QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << true << kdbx4; + QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << false << kdbx4; + QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << false << kdbx4; + QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << false << kdbx3; + QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << true << kdbx4; + QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << true << kdbx4; + QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << true << kdbx4; QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << false << kdbx4; QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4; diff --git a/tests/TestPasswordGenerator.cpp b/tests/TestPasswordGenerator.cpp new file mode 100644 index 0000000000..53cf25c312 --- /dev/null +++ b/tests/TestPasswordGenerator.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 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 "TestPasswordGenerator.h" +#include "core/PasswordGenerator.h" +#include "crypto/Crypto.h" + +#include +#include + +QTEST_GUILESS_MAIN(TestPasswordGenerator) + +void TestPasswordGenerator::initTestCase() +{ + QVERIFY(Crypto::init()); +} + +void TestPasswordGenerator::testCharClasses() +{ + PasswordGenerator generator; + QVERIFY(!generator.isValid()); + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters); + generator.setLength(16); + QVERIFY(generator.isValid()); + QCOMPARE(generator.generatePassword().size(), 16); + + generator.setLength(2000); + QString password = generator.generatePassword(); + QCOMPARE(password.size(), 2000); + QRegularExpression regex(R"(^[a-z]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::UpperLetters); + password = generator.generatePassword(); + regex.setPattern(R"(^[A-Z]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Numbers); + password = generator.generatePassword(); + regex.setPattern(R"(^\d+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Braces); + password = generator.generatePassword(); + regex.setPattern(R"(^[\(\)\[\]\{\}]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Punctuation); + password = generator.generatePassword(); + regex.setPattern(R"(^[\.,:;]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Quotes); + password = generator.generatePassword(); + regex.setPattern(R"(^["']+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Dashes); + password = generator.generatePassword(); + regex.setPattern(R"(^[\-/\\_|]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Math); + password = generator.generatePassword(); + regex.setPattern(R"(^[!\*\+\-<=>\?]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Logograms); + password = generator.generatePassword(); + regex.setPattern(R"(^[#`~%&^$@]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::EASCII); + password = generator.generatePassword(); + regex.setPattern(R"(^[^a-zA-Z0-9\.,:;"'\-/\\_|!\*\+\-<=>\?#`~%&^$@]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters + | PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Braces); + password = generator.generatePassword(); + regex.setPattern(R"(^[a-zA-Z\(\)\[\]\{\}]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Quotes + | PasswordGenerator::CharClass::Numbers | PasswordGenerator::CharClass::Dashes); + password = generator.generatePassword(); + regex.setPattern(R"(^["'\d\-/\\_|]+$)"); + QVERIFY(regex.match(password).hasMatch()); +} + +void TestPasswordGenerator::testLookalikeExclusion() +{ + PasswordGenerator generator; + generator.setLength(2000); + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters); + QVERIFY(generator.isValid()); + QString password = generator.generatePassword(); + QCOMPARE(password.size(), 2000); + + generator.setFlags(PasswordGenerator::GeneratorFlag::ExcludeLookAlike); + password = generator.generatePassword(); + QRegularExpression regex("^[^lI0]+$"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters | + PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers); + password = generator.generatePassword(); + regex.setPattern("^[^lI01]+$"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters + | PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers + | PasswordGenerator::CharClass::EASCII); + password = generator.generatePassword(); + regex.setPattern("^[^lI01ï¹’]+$"); + QVERIFY(regex.match(password).hasMatch()); +} diff --git a/tests/TestPasswordGenerator.h b/tests/TestPasswordGenerator.h new file mode 100644 index 0000000000..5287e5bdef --- /dev/null +++ b/tests/TestPasswordGenerator.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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_TESTPASSWORDGENERATOR_H +#define KEEPASSXC_TESTPASSWORDGENERATOR_H + +#include + +class TestPasswordGenerator : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testCharClasses(); + void testLookalikeExclusion(); +}; + +#endif //KEEPASSXC_TESTPASSWORDGENERATOR_H diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index bec894c06f..b69e463b1d 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -26,162 +26,173 @@ #include "streams/SymmetricCipherStream.h" QTEST_GUILESS_MAIN(TestSymmetricCipher) +Q_DECLARE_METATYPE(SymmetricCipher::Algorithm); +Q_DECLARE_METATYPE(SymmetricCipher::Mode); +Q_DECLARE_METATYPE(SymmetricCipher::Direction); void TestSymmetricCipher::initTestCase() { QVERIFY(Crypto::init()); } -void TestSymmetricCipher::testAes128CbcEncryption() +void TestSymmetricCipher::testAlgorithmToCipher() { - // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - - QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d"); - cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(plainText, &ok), cipherText); - QVERIFY(ok); - - QBuffer buffer; - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::WriteOnly); - QVERIFY(stream.open(QIODevice::WriteOnly)); - QVERIFY(stream.reset()); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(16)), qint64(16)); - QCOMPARE(buffer.data(), cipherText.left(16)); - QVERIFY(stream.reset()); - // make sure padding is written - QCOMPARE(buffer.data().size(), 32); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - QVERIFY(buffer.data().isEmpty()); - - QVERIFY(stream.reset()); - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - stream.close(); - QCOMPARE(buffer.data().size(), 16); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes128), KeePass2::CIPHER_AES128); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes256), KeePass2::CIPHER_AES256); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Twofish), KeePass2::CIPHER_TWOFISH); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::ChaCha20), KeePass2::CIPHER_CHACHA20); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::InvalidAlgorithm), QUuid()); } -void TestSymmetricCipher::testAes128CbcDecryption() +void TestSymmetricCipher::testEncryptionDecryption_data() { - QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d"); - cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2")); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(cipherText, &ok), plainText); - QVERIFY(ok); - - // padded with 16 0x10 bytes - QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538"); - QBuffer buffer(&cipherTextPadded); - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::ReadOnly); - QVERIFY(stream.open(QIODevice::ReadOnly)); + QTest::addColumn("algorithm"); + QTest::addColumn("mode"); + QTest::addColumn("direction"); + QTest::addColumn("key"); + QTest::addColumn("iv"); + QTest::addColumn("plainText"); + QTest::addColumn("cipherText"); - QCOMPARE(stream.read(10), plainText.left(10)); - buffer.reset(); - QVERIFY(stream.reset()); - QCOMPARE(stream.read(20), plainText.left(20)); - buffer.reset(); - QVERIFY(stream.reset()); - QCOMPARE(stream.read(16), plainText.left(16)); - buffer.reset(); - QVERIFY(stream.reset()); - QCOMPARE(stream.read(100), plainText); + // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + QTest::newRow("AES128-CBC Encryption") + << SymmetricCipher::Aes128 + << SymmetricCipher::Cbc + << SymmetricCipher::Encrypt + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"); + + QTest::newRow("AES128-CBC Decryption") + << SymmetricCipher::Aes128 + << SymmetricCipher::Cbc + << SymmetricCipher::Decrypt + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + + QTest::newRow("AES256-CBC Encryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Cbc + << SymmetricCipher::Encrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d"); + + QTest::newRow("AES256-CBC Decryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Cbc + << SymmetricCipher::Decrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + + QTest::newRow("AES256-CTR Encryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Ctr + << SymmetricCipher::Encrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"); + + QTest::newRow("AES256-CTR Decryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Ctr + << SymmetricCipher::Decrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + << QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); } -void TestSymmetricCipher::testAes256CbcEncryption() +void TestSymmetricCipher::testEncryptionDecryption() { - // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + QFETCH(SymmetricCipher::Algorithm, algorithm); + QFETCH(SymmetricCipher::Mode, mode); + QFETCH(SymmetricCipher::Direction, direction); + QFETCH(QByteArray, key); + QFETCH(QByteArray, iv); + QFETCH(QByteArray, plainText); + QFETCH(QByteArray, cipherText); - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + SymmetricCipher cipher(algorithm, mode, direction); QVERIFY(cipher.init(key, iv)); QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(plainText, &ok), cipherText); QVERIFY(ok); - QBuffer buffer; - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::WriteOnly); - QVERIFY(stream.open(QIODevice::WriteOnly)); - QVERIFY(stream.reset()); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(16)), qint64(16)); - QCOMPARE(buffer.data(), cipherText.left(16)); - QVERIFY(stream.reset()); - // make sure padding is written - QCOMPARE(buffer.data().size(), 32); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - QVERIFY(buffer.data().isEmpty()); - - QVERIFY(stream.reset()); - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - stream.close(); - QCOMPARE(buffer.data().size(), 16); + if (mode == SymmetricCipher::Cbc) { + QBuffer buffer; + SymmetricCipherStream stream(&buffer, algorithm, mode, direction); + QVERIFY(stream.init(key, iv)); + buffer.open(QIODevice::WriteOnly); + QVERIFY(stream.open(QIODevice::WriteOnly)); + QVERIFY(stream.reset()); + + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(16)), qint64(16)); + QCOMPARE(buffer.data(), cipherText.left(16)); + QVERIFY(stream.reset()); + // make sure padding is written + QCOMPARE(buffer.data().size(), 32); + + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); + QVERIFY(buffer.data().isEmpty()); + + QVERIFY(stream.reset()); + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); + stream.close(); + QCOMPARE(buffer.data().size(), 16); + } } -void TestSymmetricCipher::testAes256CbcDecryption() +void TestSymmetricCipher::testAesCbcPadding_data() { - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - bool ok; + QTest::addColumn("key"); + QTest::addColumn("iv"); + QTest::addColumn("cipherText"); + QTest::addColumn("plainText"); + QTest::addColumn("padding"); + + QTest::newRow("AES128") + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538"); + + QTest::newRow("AES256") + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2"); +} - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); +void TestSymmetricCipher::testAesCbcPadding() +{ + QFETCH(QByteArray, key); + QFETCH(QByteArray, iv); + QFETCH(QByteArray, cipherText); + QFETCH(QByteArray, plainText); + QFETCH(QByteArray, padding); - QCOMPARE(cipher.process(cipherText, &ok), plainText); - QVERIFY(ok); + // padded with 16 0x10 bytes + QByteArray cipherTextPadded = cipherText + padding; - // padded with 16 0x16 bytes - QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2"); QBuffer buffer(&cipherTextPadded); - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); QVERIFY(stream.init(key, iv)); buffer.open(QIODevice::ReadOnly); QVERIFY(stream.open(QIODevice::ReadOnly)); @@ -198,42 +209,48 @@ void TestSymmetricCipher::testAes256CbcDecryption() QCOMPARE(stream.read(100), plainText); } -void TestSymmetricCipher::testAes256CtrEncryption() +void TestSymmetricCipher::testInplaceEcb_data() { - // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228"); - cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, ctr)); - QCOMPARE(cipher.blockSize(), 16); - - QCOMPARE(cipher.process(plainText, &ok), cipherText); - QVERIFY(ok); + QTest::addColumn("key"); + QTest::addColumn("plainText"); + QTest::addColumn("cipherText"); + + QTest::newRow("AES128") + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a") + << QByteArray::fromHex("3ad77bb40d7a3660a89ecaf32466ef97"); } -void TestSymmetricCipher::testAes256CtrDecryption() +void TestSymmetricCipher::testInplaceEcb() { - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); - QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228"); - cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5")); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt); - QVERIFY(cipher.init(key, ctr)); - QCOMPARE(cipher.blockSize(), 16); - - QCOMPARE(cipher.process(cipherText, &ok), plainText); - QVERIFY(ok); + QFETCH(QByteArray, key); + QFETCH(QByteArray, plainText); + QFETCH(QByteArray, cipherText); + + SymmetricCipher cipherInPlaceEnc(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); + QVERIFY(cipherInPlaceEnc.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceEnc.blockSize(), 16); + auto data = QByteArray(plainText); + QVERIFY(cipherInPlaceEnc.processInPlace(data)); + QCOMPARE(data, cipherText); + + SymmetricCipher cipherInPlaceDec(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); + QVERIFY(cipherInPlaceDec.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceDec.blockSize(), 16); + QVERIFY(cipherInPlaceDec.processInPlace(data)); + QCOMPARE(data, plainText); + + SymmetricCipher cipherInPlaceEnc2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); + QVERIFY(cipherInPlaceEnc2.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceEnc2.blockSize(), 16); + data = QByteArray(plainText); + QVERIFY(cipherInPlaceEnc2.processInPlace(data, 100)); + + SymmetricCipher cipherInPlaceDec2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); + QVERIFY(cipherInPlaceDec2.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceDec2.blockSize(), 16); + QVERIFY(cipherInPlaceDec2.processInPlace(data, 100)); + QCOMPARE(data, plainText); } void TestSymmetricCipher::testTwofish256CbcEncryption() diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 9b82fd88a3..5eede0953a 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -27,12 +27,13 @@ class TestSymmetricCipher : public QObject private slots: void initTestCase(); - void testAes128CbcEncryption(); - void testAes128CbcDecryption(); - void testAes256CbcEncryption(); - void testAes256CbcDecryption(); - void testAes256CtrEncryption(); - void testAes256CtrDecryption(); + void testAlgorithmToCipher(); + void testEncryptionDecryption_data(); + void testEncryptionDecryption(); + void testAesCbcPadding_data(); + void testAesCbcPadding(); + void testInplaceEcb_data(); + void testInplaceEcb(); void testTwofish256CbcEncryption(); void testTwofish256CbcDecryption(); void testSalsa20(); diff --git a/tests/gui/CMakeLists.txt b/tests/gui/CMakeLists.txt index 6cae888303..a6e876f475 100644 --- a/tests/gui/CMakeLists.txt +++ b/tests/gui/CMakeLists.txt @@ -15,6 +15,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -add_unit_test(NAME testgui SOURCES TestGui.cpp TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testgui SOURCES TestGui.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/gui/TemporaryFile.cpp b/tests/gui/TemporaryFile.cpp deleted file mode 100644 index b6d20848b3..0000000000 --- a/tests/gui/TemporaryFile.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2016 Danny Su - * 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 "TemporaryFile.h" - -#include - -#ifdef Q_OS_WIN -const QString TemporaryFile::SUFFIX = ".win"; - -TemporaryFile::~TemporaryFile() -{ - if (m_tempFile.autoRemove()) { - m_file.remove(); - } -} -#endif - -bool TemporaryFile::open() -{ -#ifdef Q_OS_WIN - // Still call QTemporaryFile::open() so that it figures out the temporary - // file name to use. Assuming that by appending the SUFFIX to whatever - // QTemporaryFile chooses is also an available file. - bool tempFileOpened = m_tempFile.open(); - if (tempFileOpened) { - m_file.setFileName(filePath()); - return m_file.open(QIODevice::WriteOnly); - } - return false; -#else - return m_tempFile.open(); -#endif -} - -void TemporaryFile::close() -{ - m_tempFile.close(); -#ifdef Q_OS_WIN - m_file.close(); -#endif -} - -qint64 TemporaryFile::write(const char* data, qint64 maxSize) -{ -#ifdef Q_OS_WIN - return m_file.write(data, maxSize); -#else - return m_tempFile.write(data, maxSize); -#endif -} - -qint64 TemporaryFile::write(const QByteArray& byteArray) -{ -#ifdef Q_OS_WIN - return m_file.write(byteArray); -#else - return m_tempFile.write(byteArray); -#endif -} - -QString TemporaryFile::fileName() const -{ -#ifdef Q_OS_WIN - return QFileInfo(m_tempFile).fileName() + TemporaryFile::SUFFIX; -#else - return QFileInfo(m_tempFile).fileName(); -#endif -} - -QString TemporaryFile::filePath() const -{ -#ifdef Q_OS_WIN - return m_tempFile.fileName() + TemporaryFile::SUFFIX; -#else - return m_tempFile.fileName(); -#endif -} diff --git a/tests/gui/TemporaryFile.h b/tests/gui/TemporaryFile.h deleted file mode 100644 index 8a98d92359..0000000000 --- a/tests/gui/TemporaryFile.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 Danny Su - * 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 KEEPASSX_TEMPORARYFILE_H -#define KEEPASSX_TEMPORARYFILE_H - -#include -#include -#include - -/** - * QTemporaryFile::close() doesn't actually close the file according to - * http://doc.qt.io/qt-5/qtemporaryfile.html: "For as long as the - * QTemporaryFile object itself is not destroyed, the unique temporary file - * will exist and be kept open internally by QTemporaryFile." - * - * This behavior causes issues when running tests on Windows. If the file is - * not closed, the testSave test will fail due to Access Denied. The - * auto-reload test also fails from Windows not triggering file change - * notification because the file isn't actually closed by QTemporaryFile. - * - * This class isolates the Windows specific logic that uses QFile to really - * close the test file when requested to. - */ -class TemporaryFile : public QObject -{ - Q_OBJECT - -public: -#ifdef Q_OS_WIN - ~TemporaryFile(); -#endif - - bool open(); - void close(); - qint64 write(const char* data, qint64 maxSize); - qint64 write(const QByteArray& byteArray); - - QString fileName() const; - QString filePath() const; - -private: - QTemporaryFile m_tempFile; -#ifdef Q_OS_WIN - QFile m_file; - static const QString SUFFIX; -#endif -}; - -#endif // KEEPASSX_TEMPORARYFILE_H diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index e3671567cd..ee53eb7777 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -18,6 +18,7 @@ #include "TestGui.h" #include "TestGlobal.h" +#include "gui/Application.h" #include #include @@ -33,12 +34,12 @@ #include #include #include -#include #include #include #include #include "config-keepassx-tests.h" +#include "core/Bootstrap.h" #include "core/Config.h" #include "core/Database.h" #include "core/Entry.h" @@ -59,7 +60,6 @@ #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" #include "gui/FileDialog.h" -#include "gui/MainWindow.h" #include "gui/MessageBox.h" #include "gui/PasswordEdit.h" #include "gui/SearchWidget.h" @@ -74,22 +74,23 @@ #include "gui/masterkey/KeyComponentWidget.h" #include "keys/PasswordKey.h" +QTEST_MAIN(TestGui) + void TestGui::initTestCase() { QVERIFY(Crypto::init()); Config::createTempFileInstance(); // Disable autosave so we can test the modified file indicator config()->set("AutoSaveAfterEveryChange", false); - // Enable the tray icon so we can test hiding/restoring the window + // Enable the tray icon so we can test hiding/restoring the windowQByteArray config()->set("GUI/ShowTrayIcon", true); // Disable advanced settings mode (activate within individual tests to test advanced settings) config()->set("GUI/AdvancedSettings", false); - m_mainWindow = new MainWindow(); + m_mainWindow.reset(new MainWindow()); + Bootstrap::restoreMainWindowState(*m_mainWindow); m_tabWidget = m_mainWindow->findChild("tabWidget"); m_mainWindow->show(); - m_mainWindow->activateWindow(); - Tools::wait(50); // Load the NewDatabase.kdbx file into temporary storage QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx")); @@ -101,29 +102,32 @@ void TestGui::initTestCase() // Every test starts with opening the temp database void TestGui::init() { + m_dbFile.reset(new QTemporaryFile()); // Write the temp storage to a temp database file for use in our tests - QVERIFY(m_dbFile.open()); - QCOMPARE(m_dbFile.write(m_dbData), static_cast((m_dbData.size()))); - m_dbFile.close(); - - m_dbFileName = m_dbFile.fileName(); - m_dbFilePath = m_dbFile.filePath(); + QVERIFY(m_dbFile->open()); + QCOMPARE(m_dbFile->write(m_dbData), static_cast((m_dbData.size()))); + m_dbFileName = QFileInfo(m_dbFile->fileName()).fileName(); + m_dbFilePath = m_dbFile->fileName(); + m_dbFile->close(); fileDialog()->setNextFileName(m_dbFilePath); triggerAction("actionDatabaseOpen"); - QWidget* databaseOpenWidget = m_mainWindow->findChild("databaseOpenWidget"); - QLineEdit* editPassword = databaseOpenWidget->findChild("editPassword"); + auto* databaseOpenWidget = m_mainWindow->findChild("databaseOpenWidget"); + auto* editPassword = databaseOpenWidget->findChild("editPassword"); QVERIFY(editPassword); QTest::keyClicks(editPassword, "a"); QTest::keyClick(editPassword, Qt::Key_Enter); - Tools::wait(100); - QVERIFY(m_tabWidget->currentDatabaseWidget()); + QTRY_VERIFY(m_tabWidget->currentDatabaseWidget()); m_dbWidget = m_tabWidget->currentDatabaseWidget(); m_db = m_dbWidget->database(); + + // make sure window is activated or focus tests may fail + m_mainWindow->activateWindow(); + QApplication::processEvents(); } // Every test ends with closing the temp database without saving @@ -132,17 +136,21 @@ void TestGui::cleanup() // DO NOT save the database MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); + QApplication::processEvents(); if (m_db) { delete m_db; } - m_db = nullptr; - if (m_dbWidget) { delete m_dbWidget; } - m_dbWidget = nullptr; + + m_dbFile->remove(); +} + +void TestGui::cleanupTestCase() +{ + m_dbFile->remove(); } void TestGui::testSettingsDefaultTabOrder() @@ -187,8 +195,9 @@ void TestGui::testCreateDatabase() // check key and encryption QCOMPARE(m_db->key()->keys().size(), 2); + QCOMPARE(m_db->kdf()->rounds(), 2); QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2); - QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES); + QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES256); auto compositeKey = QSharedPointer::create(); compositeKey->addKey(QSharedPointer::create("test")); auto fileKey = QSharedPointer::create(); @@ -213,7 +222,40 @@ void TestGui::createDatabaseCallback() QTest::keyClick(wizard, Qt::Key_Enter); QCOMPARE(wizard->currentId(), 1); - QTest::keyClick(wizard, Qt::Key_Enter); + auto decryptionTimeSlider = wizard->currentPage()->findChild("decryptionTimeSlider"); + auto algorithmComboBox = wizard->currentPage()->findChild("algorithmComboBox"); + QTRY_VERIFY(decryptionTimeSlider->isVisible()); + QVERIFY(!algorithmComboBox->isVisible()); + auto advancedToggle = wizard->currentPage()->findChild("advancedSettingsButton"); + QTest::mouseClick(advancedToggle, Qt::MouseButton::LeftButton); + QTRY_VERIFY(!decryptionTimeSlider->isVisible()); + QVERIFY(algorithmComboBox->isVisible()); + + auto rounds = wizard->currentPage()->findChild("transformRoundsSpinBox"); + QVERIFY(rounds); + QVERIFY(rounds->isVisible()); + QTest::mouseClick(rounds, Qt::MouseButton::LeftButton); + QTest::keyClick(rounds, Qt::Key_A, Qt::ControlModifier); + QTest::keyClicks(rounds, "2"); + QTest::keyClick(rounds, Qt::Key_Tab); + QTest::keyClick(rounds, Qt::Key_Tab); + + auto memory = wizard->currentPage()->findChild("memorySpinBox"); + QVERIFY(memory); + QVERIFY(memory->isVisible()); + QTest::mouseClick(memory, Qt::MouseButton::LeftButton); + QTest::keyClick(memory, Qt::Key_A, Qt::ControlModifier); + QTest::keyClicks(memory, "50"); + QTest::keyClick(memory, Qt::Key_Tab); + + auto parallelism = wizard->currentPage()->findChild("parallelismSpinBox"); + QVERIFY(parallelism); + QVERIFY(parallelism->isVisible()); + QTest::mouseClick(parallelism, Qt::MouseButton::LeftButton); + QTest::keyClick(parallelism, Qt::Key_A, Qt::ControlModifier); + QTest::keyClicks(parallelism, "1"); + QTest::keyClick(parallelism, Qt::Key_Enter); + QCOMPARE(wizard->currentId(), 2); // enter password @@ -222,7 +264,7 @@ void TestGui::createDatabaseCallback() auto* passwordEdit = passwordWidget->findChild("enterPasswordEdit"); auto* passwordRepeatEdit = passwordWidget->findChild("repeatPasswordEdit"); QTRY_VERIFY(passwordEdit->isVisible()); - QVERIFY(passwordEdit->hasFocus()); + QTRY_VERIFY(passwordEdit->hasFocus()); QTest::keyClicks(passwordEdit, "test"); QTest::keyClick(passwordEdit, Qt::Key::Key_Tab); QTest::keyClicks(passwordRepeatEdit, "test"); @@ -247,25 +289,26 @@ void TestGui::createDatabaseCallback() QCOMPARE(fileCombo->currentText(), QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key")); // save database to temporary file - TemporaryFile tmpFile; + QTemporaryFile tmpFile; QVERIFY(tmpFile.open()); tmpFile.close(); - fileDialog()->setNextFileName(tmpFile.filePath()); + fileDialog()->setNextFileName(tmpFile.fileName()); QTest::keyClick(fileCombo, Qt::Key::Key_Enter); + tmpFile.remove(); } void TestGui::testMergeDatabase() { // It is safe to ignore the warning this line produces - QSignalSpy dbMergeSpy(m_dbWidget, SIGNAL(databaseMerged(Database*))); + QSignalSpy dbMergeSpy(m_dbWidget.data(), SIGNAL(databaseMerged(Database*))); // set file to merge from fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx")); triggerAction("actionDatabaseMerge"); - QWidget* databaseOpenMergeWidget = m_mainWindow->findChild("databaseOpenMergeWidget"); - QLineEdit* editPasswordMerge = databaseOpenMergeWidget->findChild("editPassword"); + auto* databaseOpenMergeWidget = m_mainWindow->findChild("databaseOpenMergeWidget"); + auto* editPasswordMerge = databaseOpenMergeWidget->findChild("editPassword"); QVERIFY(editPasswordMerge->isVisible()); m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseOpenMergeWidget); @@ -300,11 +343,11 @@ void TestGui::testAutoreloadDatabase() // Test accepting new file in autoreload MessageBox::setNextAnswer(QMessageBox::Yes); // Overwrite the current database with the temp data - QVERIFY(m_dbFile.open()); - QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); - m_dbFile.close(); - Tools::wait(1500); + QVERIFY(m_dbFile->open()); + QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); + m_dbFile->close(); + Tools::wait(800); m_db = m_dbWidget->database(); // the General group contains one entry from the new db data @@ -318,10 +361,10 @@ void TestGui::testAutoreloadDatabase() // Test rejecting new file in autoreload MessageBox::setNextAnswer(QMessageBox::No); // Overwrite the current temp database with a new file - m_dbFile.open(); - QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); - m_dbFile.close(); - Tools::wait(1500); + m_dbFile->open(); + QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); + m_dbFile->close(); + Tools::wait(800); m_db = m_dbWidget->database(); @@ -342,10 +385,10 @@ void TestGui::testAutoreloadDatabase() // This is saying yes to merging the entries MessageBox::setNextAnswer(QMessageBox::Yes); // Overwrite the current database with the temp data - QVERIFY(m_dbFile.open()); - QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); - m_dbFile.close(); - Tools::wait(1500); + QVERIFY(m_dbFile->open()); + QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); + m_dbFile->close(); + Tools::wait(800); m_db = m_dbWidget->database(); @@ -361,17 +404,17 @@ void TestGui::testTabs() void TestGui::testEditEntry() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); int editCount = 0; // Select the first entry in the database - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* entryView = m_dbWidget->findChild("entryView"); QModelIndex entryItem = entryView->model()->index(0, 1); Entry* entry = entryView->entryFromIndex(entryItem); clickIndex(entryItem, entryView, Qt::LeftButton); // Confirm the edit action button is enabled - QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); + auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QVERIFY(entryEditAction->isEnabled()); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QVERIFY(entryEditWidget->isVisible()); @@ -380,12 +423,12 @@ void TestGui::testEditEntry() // Edit the first entry ("Sample Entry") QTest::mouseClick(entryEditWidget, Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "_test"); // Apply the edit - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); QCOMPARE(entry->title(), QString("Sample Entry_test")); @@ -410,7 +453,7 @@ void TestGui::testEditEntry() // Test protected attributes editEntryWidget->setCurrentPage(1); - QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); + auto* attrTextEdit = editEntryWidget->findChild("attributesEdit"); QTest::mouseClick(editEntryWidget->findChild("addAttributeButton"), Qt::LeftButton); QString attrText = "TEST TEXT"; QTest::keyClicks(attrTextEdit, attrText); @@ -422,11 +465,11 @@ void TestGui::testEditEntry() editEntryWidget->setCurrentPage(0); // Test mismatch passwords - QLineEdit* passwordEdit = editEntryWidget->findChild("passwordEdit"); + auto* passwordEdit = editEntryWidget->findChild("passwordEdit"); QString originalPassword = passwordEdit->text(); passwordEdit->setText("newpass"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - MessageWidget* messageWiget = editEntryWidget->findChild("messageWidget"); + auto* messageWiget = editEntryWidget->findChild("messageWidget"); QTRY_VERIFY(messageWiget->isVisible()); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); QCOMPARE(passwordEdit->text(), QString("newpass")); @@ -469,9 +512,9 @@ void TestGui::testSearchEditEntry() // Regression test for Issue #1447 -- Uses example from issue description // Find buttons for group creation - EditGroupWidget* editGroupWidget = m_dbWidget->findChild("editGroupWidget"); - QLineEdit* nameEdit = editGroupWidget->findChild("editName"); - QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild("buttonBox"); + auto* editGroupWidget = m_dbWidget->findChild("editGroupWidget"); + auto* nameEdit = editGroupWidget->findChild("editName"); + auto* editGroupWidgetButtonBox = editGroupWidget->findChild("buttonBox"); // Add groups "Good" and "Bad" m_dbWidget->createGroup(); @@ -484,11 +527,11 @@ void TestGui::testSearchEditEntry() m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup()); // Find buttons for entry creation - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild("actionEntryNew")); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); // Create "Doggy" in "Good" Group* goodGroup = m_dbWidget->currentGroup()->findChildByName(QString("Good")); @@ -501,8 +544,8 @@ void TestGui::testSearchEditEntry() m_dbWidget->groupView()->setCurrentGroup(badGroup); // Search for "Doggy" entry - SearchWidget* searchWidget = toolBar->findChild("SearchWidget"); - QLineEdit* searchTextEdit = searchWidget->findChild("searchEdit"); + auto* searchWidget = toolBar->findChild("SearchWidget"); + auto* searchTextEdit = searchWidget->findChild("searchEdit"); QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTest::keyClicks(searchTextEdit, "Doggy"); QTRY_VERIFY(m_dbWidget->isInSearchMode()); @@ -518,11 +561,11 @@ void TestGui::testSearchEditEntry() void TestGui::testAddEntry() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryView = m_dbWidget->findChild("entryView"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -535,10 +578,10 @@ void TestGui::testAddEntry() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); @@ -551,28 +594,12 @@ void TestGui::testAddEntry() // Add entry "something 2" QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 2"); - QLineEdit* passwordEdit = editEntryWidget->findChild("passwordEdit"); - QLineEdit* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); + auto* passwordEdit = editEntryWidget->findChild("passwordEdit"); + auto* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); QTest::keyClicks(passwordEdit, "something 2"); QTest::keyClicks(passwordRepeatEdit, "something 2"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); -/* All apply tests disabled due to data loss workaround - * that disables apply button on new entry creation - * - // Add entry "something 3" using the apply button then click ok - QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QTest::keyClicks(titleEdit, "something 3"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - - // Add entry "something 4" using the apply button then click cancel - QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QTest::keyClicks(titleEdit, "something 4"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton); -*/ - // Add entry "something 5" but click cancel button (does NOT add entry) QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 5"); @@ -587,10 +614,10 @@ void TestGui::testAddEntry() void TestGui::testPasswordEntryEntropy() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -603,18 +630,18 @@ void TestGui::testPasswordEntryEntropy() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); // Open the password generator - QToolButton* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); + auto* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); QTest::mouseClick(generatorButton, Qt::LeftButton); // Type in some password - QLineEdit* editNewPassword = editEntryWidget->findChild("editNewPassword"); - QLabel* entropyLabel = editEntryWidget->findChild("entropyLabel"); - QLabel* strengthLabel = editEntryWidget->findChild("strengthLabel"); + auto* editNewPassword = editEntryWidget->findChild("editNewPassword"); + auto* entropyLabel = editEntryWidget->findChild("entropyLabel"); + auto* strengthLabel = editEntryWidget->findChild("strengthLabel"); editNewPassword->setText(""); QTest::keyClicks(editNewPassword, "hello"); @@ -659,10 +686,10 @@ void TestGui::testPasswordEntryEntropy() void TestGui::testDicewareEntryEntropy() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -675,27 +702,27 @@ void TestGui::testDicewareEntryEntropy() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); // Open the password generator - QToolButton* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); + auto* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); QTest::mouseClick(generatorButton, Qt::LeftButton); // Select Diceware - QTabWidget* tabWidget = editEntryWidget->findChild("tabWidget"); - QWidget* dicewareWidget = editEntryWidget->findChild("dicewareWidget"); + auto* tabWidget = editEntryWidget->findChild("tabWidget"); + auto* dicewareWidget = editEntryWidget->findChild("dicewareWidget"); tabWidget->setCurrentWidget(dicewareWidget); - QComboBox* comboBoxWordList = dicewareWidget->findChild("comboBoxWordList"); + auto* comboBoxWordList = dicewareWidget->findChild("comboBoxWordList"); comboBoxWordList->setCurrentText("eff_large.wordlist"); - QSpinBox* spinBoxWordCount = dicewareWidget->findChild("spinBoxWordCount"); + auto* spinBoxWordCount = dicewareWidget->findChild("spinBoxWordCount"); spinBoxWordCount->setValue(6); // Type in some password - QLabel* entropyLabel = editEntryWidget->findChild("entropyLabel"); - QLabel* strengthLabel = editEntryWidget->findChild("strengthLabel"); + auto* entropyLabel = editEntryWidget->findChild("entropyLabel"); + auto* strengthLabel = editEntryWidget->findChild("strengthLabel"); QCOMPARE(entropyLabel->text(), QString("Entropy: 77.55 bit")); QCOMPARE(strengthLabel->text(), QString("Password Quality: Good")); @@ -703,8 +730,8 @@ void TestGui::testDicewareEntryEntropy() void TestGui::testTotp() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryView = m_dbWidget->findChild("entryView"); QCOMPARE(entryView->model()->rowCount(), 1); @@ -716,36 +743,36 @@ void TestGui::testTotp() triggerAction("actionEntrySetupTotp"); - TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild("TotpSetupDialog"); + auto* setupTotpDialog = m_dbWidget->findChild("TotpSetupDialog"); - Tools::wait(100); + QApplication::processEvents(); - QLineEdit* seedEdit = setupTotpDialog->findChild("seedEdit"); + auto* seedEdit = setupTotpDialog->findChild("seedEdit"); QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; QTest::keyClicks(seedEdit, exampleSeed); - QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild("buttonBox"); + auto* setupTotpButtonBox = setupTotpDialog->findChild("buttonBox"); QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); + auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QTest::mouseClick(entryEditWidget, Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); editEntryWidget->setCurrentPage(1); - QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); + auto* attrTextEdit = editEntryWidget->findChild("attributesEdit"); QTest::mouseClick(editEntryWidget->findChild("revealAttributeButton"), Qt::LeftButton); QCOMPARE(attrTextEdit->toPlainText(), exampleSeed); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); triggerAction("actionEntryTotp"); - TotpDialog* totpDialog = m_dbWidget->findChild("TotpDialog"); - QLabel* totpLabel = totpDialog->findChild("totpLabel"); + auto* totpDialog = m_dbWidget->findChild("TotpDialog"); + auto* totpLabel = totpDialog->findChild("totpLabel"); QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp()); } @@ -755,16 +782,16 @@ void TestGui::testSearch() // Add canned entries for consistent testing Q_UNUSED(addCannedEntries()); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); - SearchWidget* searchWidget = toolBar->findChild("SearchWidget"); + auto* searchWidget = toolBar->findChild("SearchWidget"); QVERIFY(searchWidget->isEnabled()); - QLineEdit* searchTextEdit = searchWidget->findChild("searchEdit"); + auto* searchTextEdit = searchWidget->findChild("searchEdit"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* entryView = m_dbWidget->findChild("entryView"); QVERIFY(entryView->isVisible()); - QAction* clearButton = searchWidget->findChild("clearIcon"); + auto* clearButton = searchWidget->findChild("clearIcon"); QVERIFY(!clearButton->isVisible()); // Enter search @@ -801,7 +828,7 @@ void TestGui::testSearch() QTest::keyClick(searchTextEdit, Qt::Key_Down); QTRY_VERIFY(entryView->hasFocus()); // Restore focus and search text selection - QTest::keyClick(m_mainWindow, Qt::Key_F, Qt::ControlModifier); + QTest::keyClick(m_mainWindow.data(), Qt::Key_F, Qt::ControlModifier); QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING")); // Ensure Down focuses on entry view when search text is selected QTest::keyClick(searchTextEdit, Qt::Key_Down); @@ -862,7 +889,7 @@ void TestGui::testSearch() QCOMPARE(entry->title(), origTitle.append("_edited")); // Cancel search, should return to normal view - QTest::keyClick(m_mainWindow, Qt::Key_Escape); + QTest::keyClick(m_mainWindow.data(), Qt::Key_Escape); QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); } @@ -871,10 +898,10 @@ void TestGui::testDeleteEntry() // Add canned entries for consistent testing Q_UNUSED(addCannedEntries()); - GroupView* groupView = m_dbWidget->findChild("groupView"); - EntryView* entryView = m_dbWidget->findChild("entryView"); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - QAction* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); + auto* groupView = m_dbWidget->findChild("groupView"); + auto* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); @@ -934,7 +961,7 @@ void TestGui::testDeleteEntry() void TestGui::testCloneEntry() { - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* entryView = m_dbWidget->findChild("entryView"); QCOMPARE(entryView->model()->rowCount(), 1); @@ -944,8 +971,8 @@ void TestGui::testCloneEntry() triggerAction("actionEntryClone"); - CloneDialog* cloneDialog = m_dbWidget->findChild("CloneDialog"); - QDialogButtonBox* cloneButtonBox = cloneDialog->findChild("buttonBox"); + auto* cloneDialog = m_dbWidget->findChild("CloneDialog"); + auto* cloneButtonBox = cloneDialog->findChild("buttonBox"); QTest::mouseClick(cloneButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QCOMPARE(entryView->model()->rowCount(), 2); @@ -956,11 +983,11 @@ void TestGui::testCloneEntry() void TestGui::testEntryPlaceholders() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryView = m_dbWidget->findChild("entryView"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -973,14 +1000,14 @@ void TestGui::testEntryPlaceholders() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); QLineEdit* usernameEdit = editEntryWidget->findChild("usernameEdit"); QTest::keyClicks(usernameEdit, "john"); QLineEdit* urlEdit = editEntryWidget->findChild("urlEdit"); QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QCOMPARE(entryView->model()->rowCount(), 2); @@ -1000,8 +1027,8 @@ void TestGui::testEntryPlaceholders() void TestGui::testDragAndDropEntry() { - EntryView* entryView = m_dbWidget->findChild("entryView"); - GroupView* groupView = m_dbWidget->findChild("groupView"); + auto* entryView = m_dbWidget->findChild("entryView"); + auto* groupView = m_dbWidget->findChild("groupView"); QAbstractItemModel* groupModel = groupView->model(); QModelIndex sourceIndex = entryView->model()->index(0, 1); @@ -1029,11 +1056,7 @@ void TestGui::testDragAndDropGroup() // dropping parent on child is supposed to fail dragAndDropGroup(groupModel->index(0, 0, rootIndex), - groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)), - -1, - false, - "NewDatabase", - 0); + groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)), -1, false, "NewDatabase", 0); dragAndDropGroup(groupModel->index(1, 0, rootIndex), rootIndex, 0, true, "NewDatabase", 0); @@ -1063,6 +1086,7 @@ void TestGui::testSaveAs() fileInfo.refresh(); QCOMPARE(fileInfo.lastModified(), lastModified); + tmpFile.remove(); } void TestGui::testSave() @@ -1123,7 +1147,7 @@ void TestGui::testKeePass1Import() // Close the KeePass1 Database MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); + QApplication::processEvents(); } void TestGui::testDatabaseLocking() @@ -1135,13 +1159,13 @@ void TestGui::testDatabaseLocking() QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]"); - QAction* actionDatabaseMerge = m_mainWindow->findChild("actionDatabaseMerge", Qt::FindChildrenRecursively); + auto* actionDatabaseMerge = m_mainWindow->findChild("actionDatabaseMerge", Qt::FindChildrenRecursively); QCOMPARE(actionDatabaseMerge->isEnabled(), false); - QAction* actionDatabaseSave = m_mainWindow->findChild("actionDatabaseSave", Qt::FindChildrenRecursively); + auto* actionDatabaseSave = m_mainWindow->findChild("actionDatabaseSave", Qt::FindChildrenRecursively); QCOMPARE(actionDatabaseSave->isEnabled(), false); QWidget* dbWidget = m_tabWidget->currentDatabaseWidget(); - QWidget* unlockDatabaseWidget = dbWidget->findChild("unlockDatabaseWidget"); + auto* unlockDatabaseWidget = dbWidget->findChild("unlockDatabaseWidget"); QWidget* editPassword = unlockDatabaseWidget->findChild("editPassword"); QVERIFY(editPassword); @@ -1162,11 +1186,11 @@ void TestGui::testDragAndDropKdbxFiles() QMimeData badMimeData; badMimeData.setUrls({QUrl::fromLocalFile(badDatabaseFilePath)}); QDragEnterEvent badDragEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &badDragEvent); + qApp->notify(m_mainWindow.data(), &badDragEvent); QCOMPARE(badDragEvent.isAccepted(), false); QDropEvent badDropEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &badDropEvent); + qApp->notify(m_mainWindow.data(), &badDropEvent); QCOMPARE(badDropEvent.isAccepted(), false); QCOMPARE(m_tabWidget->count(), openedDatabasesCount); @@ -1175,20 +1199,19 @@ void TestGui::testDragAndDropKdbxFiles() QMimeData goodMimeData; goodMimeData.setUrls({QUrl::fromLocalFile(goodDatabaseFilePath)}); QDragEnterEvent goodDragEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &goodDragEvent); + qApp->notify(m_mainWindow.data(), &goodDragEvent); QCOMPARE(goodDragEvent.isAccepted(), true); QDropEvent goodDropEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &goodDropEvent); + qApp->notify(m_mainWindow.data(), &goodDropEvent); QCOMPARE(goodDropEvent.isAccepted(), true); QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1); MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); - QCOMPARE(m_tabWidget->count(), openedDatabasesCount); + QTRY_COMPARE(m_tabWidget->count(), openedDatabasesCount); } void TestGui::testTrayRestoreHide() @@ -1197,29 +1220,20 @@ void TestGui::testTrayRestoreHide() QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test..."); } - QSystemTrayIcon* trayIcon = m_mainWindow->findChild(); + auto* trayIcon = m_mainWindow->findChild(); QVERIFY(m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(!m_mainWindow->isVisible()); + QTRY_VERIFY(!m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(m_mainWindow->isVisible()); + QTRY_VERIFY(m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(!m_mainWindow->isVisible()); + QTRY_VERIFY(!m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(m_mainWindow->isVisible()); -} - -void TestGui::cleanupTestCase() -{ - delete m_mainWindow; + QTRY_VERIFY(m_mainWindow->isVisible()); } int TestGui::addCannedEntries() @@ -1227,17 +1241,17 @@ int TestGui::addCannedEntries() int entries_added = 0; // Find buttons - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild("actionEntryNew")); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); - QLineEdit* passwordEdit = editEntryWidget->findChild("passwordEdit"); - QLineEdit* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* passwordEdit = editEntryWidget->findChild("passwordEdit"); + auto* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); // Add entry "test" and confirm added QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "test"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); ++entries_added; @@ -1274,7 +1288,7 @@ void TestGui::checkDatabase(QString dbFileName) void TestGui::triggerAction(const QString& name) { - QAction* action = m_mainWindow->findChild(name); + auto* action = m_mainWindow->findChild(name); QVERIFY(action); QVERIFY(action->isEnabled()); action->trigger(); @@ -1312,5 +1326,3 @@ void TestGui::clickIndex(const QModelIndex& index, { QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center()); } - -QTEST_MAIN(TestGui) diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index dc8a05e9b8..5e0b441b1d 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -19,17 +19,18 @@ #ifndef KEEPASSX_TESTGUI_H #define KEEPASSX_TESTGUI_H -#include "TemporaryFile.h" +#include "gui/MainWindow.h" #include #include #include +#include +#include class Database; class DatabaseTabWidget; class DatabaseWidget; class QAbstractItemView; -class MainWindow; class TestGui : public QObject { @@ -84,12 +85,12 @@ private slots: Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0); - QPointer m_mainWindow; + QScopedPointer m_mainWindow; QPointer m_tabWidget; QPointer m_dbWidget; QPointer m_db; QByteArray m_dbData; - TemporaryFile m_dbFile; + QScopedPointer m_dbFile; QString m_dbFileName; QString m_dbFilePath; };