diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 8f2fea742..b218df7e2 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -69,16 +69,15 @@ Add::Add() int Add::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); auto& entryPath = args.at(1); // Cannot use those 2 options at the same time! if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) { - errorTextStream << QObject::tr("Cannot generate a password and prompt at the same time!") << endl; + err << QObject::tr("Cannot generate a password and prompt at the same time!") << endl; return EXIT_FAILURE; } @@ -94,7 +93,7 @@ int Add::executeWithDatabase(QSharedPointer database, QSharedPointerrootGroup()->addEntryWithPath(entryPath); if (!entry) { - errorTextStream << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl; + err << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -108,9 +107,9 @@ int Add::executeWithDatabase(QSharedPointer database, QSharedPointerisSet(Add::PasswordPromptOption)) { if (!parser->isSet(Command::QuietOption)) { - outputTextStream << QObject::tr("Enter password for new entry: ") << flush; + out << QObject::tr("Enter password for new entry: ") << flush; } - QString password = Utils::getPassword(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT); + QString password = Utils::getPassword(parser->isSet(Command::QuietOption)); entry->setPassword(password); } else if (parser->isSet(Add::GenerateOption)) { QString password = passwordGenerator->generatePassword(); @@ -119,12 +118,12 @@ int Add::executeWithDatabase(QSharedPointer database, QSharedPointersave(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; + err << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; return EXIT_FAILURE; } if (!parser->isSet(Command::QuietOption)) { - outputTextStream << QObject::tr("Successfully added entry %1.").arg(entry->title()) << endl; + out << QObject::tr("Successfully added entry %1.").arg(entry->title()) << endl; } return EXIT_SUCCESS; } diff --git a/src/cli/AddGroup.cpp b/src/cli/AddGroup.cpp index cd005cc3d..be6bfbf32 100644 --- a/src/cli/AddGroup.cpp +++ b/src/cli/AddGroup.cpp @@ -39,8 +39,8 @@ AddGroup::~AddGroup() int AddGroup::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); const QString& groupPath = args.at(1); @@ -51,13 +51,13 @@ int AddGroup::executeWithDatabase(QSharedPointer database, QSharedPoin Group* group = database->rootGroup()->findGroupByPath(groupPath); if (group) { - errorTextStream << QObject::tr("Group %1 already exists!").arg(groupPath) << endl; + err << QObject::tr("Group %1 already exists!").arg(groupPath) << endl; return EXIT_FAILURE; } Group* parentGroup = database->rootGroup()->findGroupByPath(parentGroupPath); if (!parentGroup) { - errorTextStream << QObject::tr("Group %1 not found.").arg(parentGroupPath) << endl; + err << QObject::tr("Group %1 not found.").arg(parentGroupPath) << endl; return EXIT_FAILURE; } @@ -68,12 +68,12 @@ int AddGroup::executeWithDatabase(QSharedPointer database, QSharedPoin QString errorMessage; if (!database->save(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; + err << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; return EXIT_FAILURE; } if (!parser->isSet(Command::QuietOption)) { - outputTextStream << QObject::tr("Successfully added group %1.").arg(groupName) << endl; + out << QObject::tr("Successfully added group %1.").arg(groupName) << endl; } return EXIT_SUCCESS; } diff --git a/src/cli/Analyze.cpp b/src/cli/Analyze.cpp index 6095e988b..64eac9e70 100644 --- a/src/cli/Analyze.cpp +++ b/src/cli/Analyze.cpp @@ -43,29 +43,27 @@ Analyze::Analyze() int Analyze::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption); QFile hibpFile(hibpDatabase); if (!hibpFile.open(QFile::ReadOnly)) { - errorTextStream << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) - << endl; + err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl; return EXIT_FAILURE; } - outputTextStream << QObject::tr("Evaluating database entries against HIBP file, this will take a while...") << endl; + out << QObject::tr("Evaluating database entries against HIBP file, this will take a while...") << endl; QList> findings; QString error; if (!HibpOffline::report(database, hibpFile, findings, &error)) { - errorTextStream << error << endl; + err << error << endl; return EXIT_FAILURE; } for (auto& finding : findings) { - printHibpFinding(finding.first, finding.second, outputTextStream); + printHibpFinding(finding.first, finding.second, out); } return EXIT_SUCCESS; diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index ccb7c0e53..1bd5cc4ba 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -53,32 +53,32 @@ Clip::Clip() int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; + const QStringList args = parser->positionalArguments(); const QString& entryPath = args.at(1); QString timeout; if (args.size() == 3) { timeout = args.at(2); } - TextStream errorTextStream(Utils::STDERR); int timeoutSeconds = 0; if (!timeout.isEmpty() && timeout.toInt() <= 0) { - errorTextStream << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl; + err << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl; return EXIT_FAILURE; } else if (!timeout.isEmpty()) { timeoutSeconds = timeout.toInt(); } - TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, - QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - errorTextStream << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; + err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; } if (parser->isSet(AttributeOption) && parser->isSet(TotpOption)) { - errorTextStream << QObject::tr("ERROR: Please specify one of --attribute or --totp, not both.") << endl; + err << QObject::tr("ERROR: Please specify one of --attribute or --totp, not both.") << endl; return EXIT_FAILURE; } @@ -87,7 +87,7 @@ int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer< bool found = false; if (parser->isSet(TotpOption) || selectedAttribute == "totp") { if (!entry->hasTotp()) { - errorTextStream << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << endl; + err << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -96,9 +96,9 @@ int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer< } else { QStringList attrs = Utils::findAttributes(*entry->attributes(), selectedAttribute); if (attrs.size() > 1) { - errorTextStream << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.") - .arg(selectedAttribute, QLocale().createSeparatedList(attrs)) - << endl; + err << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.") + .arg(selectedAttribute, QLocale().createSeparatedList(attrs)) + << endl; return EXIT_FAILURE; } else if (attrs.size() == 1) { found = true; @@ -108,7 +108,7 @@ int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer< } if (!found) { - outputTextStream << QObject::tr("Attribute \"%1\" not found.").arg(selectedAttribute) << endl; + out << QObject::tr("Attribute \"%1\" not found.").arg(selectedAttribute) << endl; return EXIT_FAILURE; } @@ -117,7 +117,7 @@ int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer< return exitCode; } - outputTextStream << QObject::tr("Entry's \"%1\" attribute copied to the clipboard!").arg(selectedAttribute) << endl; + out << QObject::tr("Entry's \"%1\" attribute copied to the clipboard!").arg(selectedAttribute) << endl; if (!timeoutSeconds) { return exitCode; @@ -125,15 +125,15 @@ int Clip::executeWithDatabase(QSharedPointer database, QSharedPointer< QString lastLine = ""; while (timeoutSeconds > 0) { - outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; + out << '\r' << QString(lastLine.size(), ' ') << '\r'; lastLine = QObject::tr("Clearing the clipboard in %1 second(s)...", "", timeoutSeconds).arg(timeoutSeconds); - outputTextStream << lastLine << flush; + out << lastLine << flush; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); --timeoutSeconds; } Utils::clipText(""); - outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; - outputTextStream << QObject::tr("Clipboard cleared!") << endl; + out << '\r' << QString(lastLine.size(), ' ') << '\r'; + out << QObject::tr("Clipboard cleared!") << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index e76f633ef..d352caace 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -126,24 +126,24 @@ QString Command::getHelpText() QSharedPointer Command::getCommandLineParser(const QStringList& arguments) { - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& err = Utils::STDERR; QSharedPointer parser = buildParser(this); if (!parser->parse(arguments)) { - errorTextStream << parser->errorText() << "\n\n"; - errorTextStream << getHelpText(); + err << parser->errorText() << "\n\n"; + err << getHelpText(); return {}; } if (parser->positionalArguments().size() < positionalArguments.size()) { - errorTextStream << getHelpText(); + err << getHelpText(); return {}; } if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) { - errorTextStream << getHelpText(); + err << getHelpText(); return {}; } if (parser->isSet(HelpOption)) { - errorTextStream << getHelpText(); + err << getHelpText(); return {}; } return parser; diff --git a/src/cli/Create.cpp b/src/cli/Create.cpp index 2a01097f3..f72c2b7a5 100644 --- a/src/cli/Create.cpp +++ b/src/cli/Create.cpp @@ -77,10 +77,8 @@ int Create::execute(const QStringList& arguments) return EXIT_FAILURE; } - bool quiet = parser->isSet(Command::QuietOption); - - QTextStream out(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly); - QTextStream err(Utils::STDERR, QIODevice::WriteOnly); + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); @@ -110,7 +108,7 @@ int Create::execute(const QStringList& arguments) auto key = QSharedPointer::create(); if (parser->isSet(Create::SetPasswordOption)) { - auto passwordKey = Utils::getPasswordFromStdin(); + auto passwordKey = Utils::getConfirmedPassword(); if (passwordKey.isNull()) { err << QObject::tr("Failed to set database password.") << endl; return EXIT_FAILURE; @@ -151,7 +149,7 @@ int Create::execute(const QStringList& arguments) bool ok = db->changeKdf(kdf); if (!ok) { - err << QObject::tr("Error while setting database key derivation settings.") << endl; + err << QObject::tr("error while setting database key derivation settings.") << endl; return EXIT_FAILURE; } } @@ -180,8 +178,7 @@ int Create::execute(const QStringList& arguments) */ bool Create::loadFileKey(const QString& path, QSharedPointer& fileKey) { - QTextStream err(Utils::STDERR, QIODevice::WriteOnly); - + auto& err = Utils::STDERR; QString error; fileKey = QSharedPointer(new FileKey()); diff --git a/src/cli/DatabaseCommand.cpp b/src/cli/DatabaseCommand.cpp index 56e565baa..08104c120 100644 --- a/src/cli/DatabaseCommand.cpp +++ b/src/cli/DatabaseCommand.cpp @@ -57,8 +57,7 @@ int DatabaseCommand::execute(const QStringList& arguments) #else "", #endif - parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, - Utils::STDERR); + parser->isSet(Command::QuietOption)); if (!db) { return EXIT_FAILURE; } diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index ef1e10a80..d0deb9072 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -51,8 +51,8 @@ int Diceware::execute(const QStringList& arguments) return EXIT_FAILURE; } - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; PassphraseGenerator dicewareGenerator; @@ -60,7 +60,7 @@ int Diceware::execute(const QStringList& arguments) if (wordCount.isEmpty()) { dicewareGenerator.setWordCount(PassphraseGenerator::DefaultWordCount); } else if (wordCount.toInt() <= 0) { - errorTextStream << QObject::tr("Invalid word count %1").arg(wordCount) << endl; + err << QObject::tr("Invalid word count %1").arg(wordCount) << endl; return EXIT_FAILURE; } else { dicewareGenerator.setWordCount(wordCount.toInt()); @@ -74,12 +74,12 @@ int Diceware::execute(const QStringList& arguments) if (!dicewareGenerator.isValid()) { // We already validated the word count input so if the generator is invalid, it // must be because the word list is too small. - errorTextStream << QObject::tr("The word list is too small (< 1000 items)") << endl; + err << QObject::tr("The word list is too small (< 1000 items)") << endl; 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 30dccf15e..6fd1b0369 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -60,16 +60,15 @@ Edit::Edit() int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, - QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); const QString& entryPath = args.at(1); // Cannot use those 2 options at the same time! if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) { - errorTextStream << QObject::tr("Cannot generate a password and prompt at the same time!") << endl; + err << QObject::tr("Cannot generate a password and prompt at the same time!") << endl; return EXIT_FAILURE; } @@ -86,7 +85,7 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -95,7 +94,7 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< QString title = parser->value(Edit::TitleOption); bool prompt = parser->isSet(Add::PasswordPromptOption); if (username.isEmpty() && url.isEmpty() && title.isEmpty() && !prompt && !generate) { - errorTextStream << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl; + err << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -114,8 +113,8 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< } if (prompt) { - outputTextStream << QObject::tr("Enter new password for entry: ") << flush; - QString password = Utils::getPassword(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT); + out << QObject::tr("Enter new password for entry: ") << flush; + QString password = Utils::getPassword(parser->isSet(Command::QuietOption)); entry->setPassword(password); } else if (generate) { QString password = passwordGenerator->generatePassword(); @@ -126,10 +125,10 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< QString errorMessage; if (!database->save(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl; + err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << QObject::tr("Successfully edited entry %1.").arg(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 3b7509057..5576e2362 100644 --- a/src/cli/Estimate.cpp +++ b/src/cli/Estimate.cpp @@ -48,7 +48,7 @@ Estimate::Estimate() static void estimate(const char* pwd, bool advanced) { - TextStream out(Utils::STDOUT, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; int len = static_cast(strlen(pwd)); if (!advanced) { @@ -163,14 +163,14 @@ int Estimate::execute(const QStringList& arguments) return EXIT_FAILURE; } - TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); + auto& in = Utils::STDIN; const QStringList args = parser->positionalArguments(); QString password; if (args.size() == 1) { password = args.at(0); } else { - password = inputTextStream.readLine(); + password = in.readLine(); } estimate(password.toLatin1(), parser->isSet(Estimate::AdvancedOption)); diff --git a/src/cli/Export.cpp b/src/cli/Export.cpp index 2f2ee65e5..930324daa 100644 --- a/src/cli/Export.cpp +++ b/src/cli/Export.cpp @@ -40,23 +40,23 @@ Export::Export() int Export::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + TextStream out(Utils::STDOUT.device()); + auto& err = Utils::STDERR; QString format = parser->value(Export::FormatOption); if (format.isEmpty() || format.startsWith(QStringLiteral("xml"), Qt::CaseInsensitive)) { QByteArray xmlData; QString errorMessage; if (!database->extract(xmlData, &errorMessage)) { - errorTextStream << QObject::tr("Unable to export database to XML: %1").arg(errorMessage) << endl; + err << QObject::tr("Unable to export database to XML: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream.write(xmlData.constData()); + out.write(xmlData.constData()); } else if (format.startsWith(QStringLiteral("csv"), Qt::CaseInsensitive)) { CsvExporter csvExporter; - outputTextStream << csvExporter.exportDatabase(database); + out << csvExporter.exportDatabase(database); } else { - errorTextStream << QObject::tr("Unsupported format %1").arg(format) << endl; + err << QObject::tr("Unsupported format %1").arg(format) << endl; return EXIT_FAILURE; } diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index dc4add242..c4761e6f2 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -80,13 +80,13 @@ Generate::Generate() */ QSharedPointer Generate::createGenerator(QSharedPointer parser) { - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& err = Utils::STDERR; QSharedPointer passwordGenerator = QSharedPointer(new PasswordGenerator()); QString passwordLength = parser->value(Generate::PasswordLengthOption); if (passwordLength.isEmpty()) { passwordGenerator->setLength(PasswordGenerator::DefaultLength); } else if (passwordLength.toInt() <= 0) { - errorTextStream << QObject::tr("Invalid password length %1").arg(passwordLength) << endl; + err << QObject::tr("Invalid password length %1").arg(passwordLength) << endl; return QSharedPointer(nullptr); } else { passwordGenerator->setLength(passwordLength.toInt()); @@ -126,7 +126,7 @@ QSharedPointer Generate::createGenerator(QSharedPointersetExcludedChars(parser->value(Generate::ExcludeCharsOption)); if (!passwordGenerator->isValid()) { - errorTextStream << QObject::tr("Invalid password generator after applying all options") << endl; + err << QObject::tr("Invalid password generator after applying all options") << endl; return QSharedPointer(nullptr); } @@ -145,9 +145,9 @@ int Generate::execute(const QStringList& arguments) return EXIT_FAILURE; } - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; QString password = passwordGenerator->generatePassword(); - outputTextStream << password << endl; + out << password << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Help.cpp b/src/cli/Help.cpp index 193f55dd7..07a3f085f 100644 --- a/src/cli/Help.cpp +++ b/src/cli/Help.cpp @@ -29,7 +29,7 @@ Help::Help() int Help::execute(const QStringList& arguments) { - TextStream out(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; QSharedPointer command; if (arguments.size() > 1 && (command = Commands::getCommand(arguments.at(1)))) { out << command->getHelpText(); @@ -39,5 +39,6 @@ int Help::execute(const QStringList& arguments) out << cmd->getDescriptionLine(); } } + out.flush(); return EXIT_SUCCESS; } diff --git a/src/cli/Import.cpp b/src/cli/Import.cpp index 9331cd078..930158988 100644 --- a/src/cli/Import.cpp +++ b/src/cli/Import.cpp @@ -55,30 +55,29 @@ int Import::execute(const QStringList& arguments) return EXIT_FAILURE; } - TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, - QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); const QString& xmlExportPath = args.at(0); const QString& dbPath = args.at(1); if (QFileInfo::exists(dbPath)) { - errorTextStream << QObject::tr("File %1 already exists.").arg(dbPath) << endl; + err << QObject::tr("File %1 already exists.").arg(dbPath) << endl; return EXIT_FAILURE; } auto key = QSharedPointer::create(); - auto passwordKey = Utils::getPasswordFromStdin(); + auto passwordKey = Utils::getConfirmedPassword(); if (passwordKey.isNull()) { - errorTextStream << QObject::tr("Failed to set database password.") << endl; + err << QObject::tr("Failed to set database password.") << endl; return EXIT_FAILURE; } key->addKey(passwordKey); if (key->isEmpty()) { - errorTextStream << QObject::tr("No key is set. Aborting database creation.") << endl; + err << QObject::tr("No key is set. Aborting database creation.") << endl; return EXIT_FAILURE; } @@ -88,15 +87,15 @@ int Import::execute(const QStringList& arguments) db.setKey(key); if (!db.import(xmlExportPath, &errorMessage)) { - errorTextStream << QObject::tr("Unable to import XML database: %1").arg(errorMessage) << endl; + err << QObject::tr("Unable to import XML database: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } if (!db.saveAs(dbPath, &errorMessage, true, false)) { - errorTextStream << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl; + err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << QObject::tr("Successfully imported database.") << endl; + out << QObject::tr("Successfully imported database.") << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Info.cpp b/src/cli/Info.cpp index 2aed1803c..c57472770 100644 --- a/src/cli/Info.cpp +++ b/src/cli/Info.cpp @@ -33,7 +33,7 @@ Info::Info() int Info::executeWithDatabase(QSharedPointer database, QSharedPointer) { - TextStream out(Utils::STDOUT, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; out << QObject::tr("UUID: ") << database->uuid().toString() << endl; out << QObject::tr("Name: ") << database->metadata()->name() << endl; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index d068cdf53..ba00a7162 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -47,8 +47,8 @@ List::List() int List::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); bool recursive = parser->isSet(List::RecursiveOption); @@ -56,17 +56,17 @@ int List::executeWithDatabase(QSharedPointer database, QSharedPointer< // No group provided, defaulting to root group. if (args.size() == 1) { - outputTextStream << database->rootGroup()->print(recursive, flatten) << flush; + out << database->rootGroup()->print(recursive, flatten) << flush; return EXIT_SUCCESS; } const QString& groupPath = args.at(1); Group* group = database->rootGroup()->findGroupByPath(groupPath); if (!group) { - errorTextStream << QObject::tr("Cannot find group %1.").arg(groupPath) << endl; + err << QObject::tr("Cannot find group %1.").arg(groupPath) << endl; return EXIT_FAILURE; } - outputTextStream << group->print(recursive, flatten) << flush; + out << group->print(recursive, flatten) << flush; return EXIT_SUCCESS; } diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index 9b574852d..a7a1e06f2 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -38,20 +38,20 @@ Locate::Locate() int Locate::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); const QString& searchTerm = args.at(1); - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); QStringList results = database->rootGroup()->locate(searchTerm); if (results.isEmpty()) { - errorTextStream << "No results for that search term." << endl; + err << "No results for that search term." << endl; return EXIT_FAILURE; } for (const QString& result : asConst(results)) { - outputTextStream << result << endl; + out << result << endl; } return EXIT_SUCCESS; } diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 5855eff46..ce7ecc558 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -61,9 +61,8 @@ Merge::Merge() int Merge::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, - QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); @@ -76,8 +75,7 @@ int Merge::executeWithDatabase(QSharedPointer database, QSharedPointer !parser->isSet(Merge::NoPasswordFromOption), parser->value(Merge::KeyFileFromOption), parser->value(Merge::YubiKeyFromOption), - parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, - Utils::STDERR); + parser->isSet(Command::QuietOption)); if (!db2) { return EXIT_FAILURE; } @@ -85,7 +83,7 @@ int Merge::executeWithDatabase(QSharedPointer database, QSharedPointer db2 = QSharedPointer::create(); QString errorMessage; if (!db2->open(fromDatabasePath, database->key(), &errorMessage, false)) { - errorTextStream << QObject::tr("Error reading merge file:\n%1").arg(errorMessage); + err << QObject::tr("Error reading merge file:\n%1").arg(errorMessage); return EXIT_FAILURE; } } @@ -94,19 +92,18 @@ int Merge::executeWithDatabase(QSharedPointer database, QSharedPointer QStringList changeList = merger.merge(); for (auto& mergeChange : changeList) { - outputTextStream << "\t" << mergeChange << endl; + out << "\t" << mergeChange << endl; } if (!changeList.isEmpty() && !parser->isSet(Merge::DryRunOption)) { QString errorMessage; if (!database->save(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl; + err << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << QObject::tr("Successfully merged %1 into %2.").arg(fromDatabasePath, toDatabasePath) - << endl; + out << QObject::tr("Successfully merged %1 into %2.").arg(fromDatabasePath, toDatabasePath) << endl; } else { - outputTextStream << QObject::tr("Database was not modified by merge operation.") << endl; + out << QObject::tr("Database was not modified by merge operation.") << endl; } return EXIT_SUCCESS; diff --git a/src/cli/Move.cpp b/src/cli/Move.cpp index 7a18383ed..74070372d 100644 --- a/src/cli/Move.cpp +++ b/src/cli/Move.cpp @@ -40,8 +40,8 @@ Move::~Move() int Move::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); const QString& entryPath = args.at(1); @@ -49,18 +49,18 @@ int Move::executeWithDatabase(QSharedPointer database, QSharedPointer< Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } Group* destinationGroup = database->rootGroup()->findGroupByPath(destinationPath); if (!destinationGroup) { - errorTextStream << QObject::tr("Could not find group with path %1.").arg(destinationPath) << endl; + err << QObject::tr("Could not find group with path %1.").arg(destinationPath) << endl; return EXIT_FAILURE; } if (destinationGroup == entry->parent()) { - errorTextStream << QObject::tr("Entry is already in group %1.").arg(destinationPath) << endl; + err << QObject::tr("Entry is already in group %1.").arg(destinationPath) << endl; return EXIT_FAILURE; } @@ -70,11 +70,10 @@ int Move::executeWithDatabase(QSharedPointer database, QSharedPointer< QString errorMessage; if (!database->save(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; + err << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << QObject::tr("Successfully moved entry %1 to group %2.").arg(entry->title(), destinationPath) - << endl; + out << QObject::tr("Successfully moved entry %1 to group %2.").arg(entry->title(), destinationPath) << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index e0dc0d58a..6a3a87b00 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -37,15 +37,13 @@ Remove::Remove() int Remove::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - bool quiet = parser->isSet(Command::QuietOption); + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; + auto& entryPath = parser->positionalArguments().at(1); - - TextStream outputTextStream(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); - QPointer entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - errorTextStream << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; + err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -61,14 +59,14 @@ int Remove::executeWithDatabase(QSharedPointer database, QSharedPointe QString errorMessage; if (!database->save(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; + err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } if (recycled) { - outputTextStream << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl; + out << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl; } else { - outputTextStream << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl; + out << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl; } return EXIT_SUCCESS; diff --git a/src/cli/RemoveGroup.cpp b/src/cli/RemoveGroup.cpp index b65ae1974..5ebac16f9 100644 --- a/src/cli/RemoveGroup.cpp +++ b/src/cli/RemoveGroup.cpp @@ -41,21 +41,20 @@ RemoveGroup::~RemoveGroup() int RemoveGroup::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - bool quiet = parser->isSet(Command::QuietOption); - QString groupPath = parser->positionalArguments().at(1); + auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT; + auto& err = Utils::STDERR; - TextStream outputTextStream(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + QString groupPath = parser->positionalArguments().at(1); // Recursive option means were looking for a group to remove. QPointer group = database->rootGroup()->findGroupByPath(groupPath); if (!group) { - errorTextStream << QObject::tr("Group %1 not found.").arg(groupPath) << endl; + err << QObject::tr("Group %1 not found.").arg(groupPath) << endl; return EXIT_FAILURE; } if (group == database->rootGroup()) { - errorTextStream << QObject::tr("Cannot remove root group from database.") << endl; + err << QObject::tr("Cannot remove root group from database.") << endl; return EXIT_FAILURE; } @@ -70,14 +69,14 @@ int RemoveGroup::executeWithDatabase(QSharedPointer database, QSharedP QString errorMessage; if (!database->save(&errorMessage, true, false)) { - errorTextStream << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; + err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } if (recycled) { - outputTextStream << QObject::tr("Successfully recycled group %1.").arg(groupPath) << endl; + out << QObject::tr("Successfully recycled group %1.").arg(groupPath) << endl; } else { - outputTextStream << QObject::tr("Successfully deleted group %1.").arg(groupPath) << endl; + out << QObject::tr("Successfully deleted group %1.").arg(groupPath) << endl; } return EXIT_SUCCESS; diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 12b2a835f..02ec4677e 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -59,8 +59,8 @@ Show::Show() int Show::executeWithDatabase(QSharedPointer database, QSharedPointer parser) { - TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; const QStringList args = parser->positionalArguments(); const QString& entryPath = args.at(1); @@ -70,12 +70,12 @@ int Show::executeWithDatabase(QSharedPointer database, QSharedPointer< Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } if (showTotp && !entry->hasTotp()) { - errorTextStream << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << endl; + err << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -91,28 +91,28 @@ int Show::executeWithDatabase(QSharedPointer database, QSharedPointer< QStringList attrs = Utils::findAttributes(*entry->attributes(), attributeName); if (attrs.isEmpty()) { encounteredError = true; - errorTextStream << QObject::tr("ERROR: unknown attribute %1.").arg(attributeName) << endl; + err << QObject::tr("ERROR: unknown attribute %1.").arg(attributeName) << endl; continue; } else if (attrs.size() > 1) { encounteredError = true; - errorTextStream << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.") - .arg(attributeName, QLocale().createSeparatedList(attrs)) - << endl; + err << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.") + .arg(attributeName, QLocale().createSeparatedList(attrs)) + << endl; continue; } QString canonicalName = attrs[0]; if (showDefaultAttributes) { - outputTextStream << canonicalName << ": "; + out << canonicalName << ": "; } if (entry->attributes()->isProtected(canonicalName) && showDefaultAttributes && !showProtectedAttributes) { - outputTextStream << "PROTECTED" << endl; + out << "PROTECTED" << endl; } else { - outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(canonicalName)) << endl; + out << entry->resolveMultiplePlaceholders(entry->attributes()->value(canonicalName)) << endl; } } if (showTotp) { - outputTextStream << entry->totp() << endl; + out << entry->totp() << endl; } return encounteredError ? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index ab7c23186..e8561a1cd 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -30,29 +30,33 @@ namespace Utils { - /** - * STDOUT file handle for the CLI. - */ - FILE* STDOUT = stdout; + QTextStream STDOUT; + QTextStream STDERR; + QTextStream STDIN; + QTextStream DEVNULL; - /** - * STDERR file handle for the CLI. - */ - FILE* STDERR = stderr; + void setDefaultTextStreams() + { + auto fd = new QFile(); + fd->open(stdout, QIODevice::WriteOnly); + STDOUT.setDevice(fd); - /** - * STDIN file handle for the CLI. - */ - FILE* STDIN = stdin; + fd = new QFile(); + fd->open(stderr, QIODevice::WriteOnly); + STDERR.setDevice(fd); -/** - * DEVNULL file handle for the CLI. - */ + fd = new QFile(); + fd->open(stdin, QIODevice::ReadOnly); + STDIN.setDevice(fd); + + fd = new QFile(); #ifdef Q_OS_WIN - FILE* DEVNULL = fopen("nul", "w"); + fd->open(fopen("nul", "w"), QIODevice::WriteOnly); #else - FILE* DEVNULL = fopen("/dev/null", "w"); + fd->open(fopen("/dev/null", "w"), QIODevice::WriteOnly); #endif + DEVNULL.setDevice(fd); + } void setStdinEcho(bool enable = true) { @@ -82,36 +86,14 @@ namespace Utils #endif } - namespace Test - { - 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, bool repeat) - { - nextPasswords.append(password); - if (repeat) { - nextPasswords.append(password); - } - } - } // namespace Test - QSharedPointer unlockDatabase(const QString& databaseFilename, const bool isPasswordProtected, const QString& keyFilename, const QString& yubiKeySlot, - FILE* outputDescriptor, - FILE* errorDescriptor) + bool quiet) { + auto& err = quiet ? DEVNULL : STDERR; auto compositeKey = QSharedPointer::create(); - TextStream out(outputDescriptor); - TextStream err(errorDescriptor); QFileInfo dbFileInfo(databaseFilename); if (dbFileInfo.canonicalFilePath().isEmpty()) { @@ -130,8 +112,8 @@ namespace Utils } if (isPasswordProtected) { - out << QObject::tr("Enter password to unlock %1: ").arg(databaseFilename) << flush; - QString line = Utils::getPassword(outputDescriptor); + err << QObject::tr("Enter password to unlock %1: ").arg(databaseFilename) << flush; + QString line = Utils::getPassword(quiet); auto passwordKey = QSharedPointer::create(); passwordKey->setPassword(line); compositeKey->addKey(passwordKey); @@ -177,7 +159,7 @@ namespace Utils slot, blocking, QObject::tr("Please touch the button on your YubiKey to unlock %1").arg(databaseFilename), - outputDescriptor)); + err.device())); compositeKey->addChallengeResponseKey(key); } #else @@ -200,19 +182,10 @@ namespace Utils * * @return the password */ - QString getPassword(FILE* outputDescriptor) + QString getPassword(bool quiet) { - TextStream out(outputDescriptor, QIODevice::WriteOnly); - - // return preset password if one is set - if (!Test::nextPasswords.isEmpty()) { - auto password = Test::nextPasswords.takeFirst(); - // simulate user entering newline - out << endl; - return password; - } - - static TextStream in(STDIN, QIODevice::ReadOnly); + auto& in = STDIN; + auto& out = quiet ? DEVNULL : STDERR; setStdinEcho(false); QString line = in.readLine(); @@ -228,37 +201,34 @@ namespace Utils * @return Pointer to the PasswordKey or null if passwordkey is skipped * by user */ - QSharedPointer getPasswordFromStdin() + QSharedPointer getConfirmedPassword() { - QSharedPointer passwordKey; - QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + auto& err = STDERR; + auto& in = STDIN; - out << QObject::tr("Enter password to encrypt database (optional): "); - out.flush(); + QSharedPointer passwordKey; + + err << QObject::tr("Enter password to encrypt database (optional): "); + err.flush(); auto password = Utils::getPassword(); if (password.isEmpty()) { - out << QObject::tr("Do you want to create a database with an empty password? [y/N]: "); - out.flush(); - TextStream ts(STDIN, QIODevice::ReadOnly); - if (!ts.device()->isSequential()) { - // This is required for testing on macOS - ts.seek(0); - } - auto ans = ts.readLine(); + err << QObject::tr("Do you want to create a database with an empty password? [y/N]: "); + err.flush(); + auto ans = in.readLine(); if (ans.toLower().startsWith("y")) { passwordKey = QSharedPointer::create(""); } - out << endl; + err << endl; } else { - out << QObject::tr("Repeat password: "); - out.flush(); + err << QObject::tr("Repeat password: "); + err.flush(); auto repeat = Utils::getPassword(); if (password == repeat) { passwordKey = QSharedPointer::create(password); } else { - out << QObject::tr("Error: Passwords do not match.") << endl; + err << QObject::tr("Error: Passwords do not match.") << endl; } } @@ -271,7 +241,7 @@ namespace Utils */ int clipText(const QString& text) { - TextStream err(Utils::STDERR); + auto& err = STDERR; // List of programs and their arguments QList> clipPrograms; diff --git a/src/cli/Utils.h b/src/cli/Utils.h index 512b209d6..2e7ee8e1d 100644 --- a/src/cli/Utils.h +++ b/src/cli/Utils.h @@ -34,21 +34,22 @@ namespace Utils { - extern FILE* STDOUT; - extern FILE* STDERR; - extern FILE* STDIN; - extern FILE* DEVNULL; + extern QTextStream STDOUT; + extern QTextStream STDERR; + extern QTextStream STDIN; + extern QTextStream DEVNULL; + + void setDefaultTextStreams(); void setStdinEcho(bool enable); - QString getPassword(FILE* outputDescriptor = STDOUT); - QSharedPointer getPasswordFromStdin(); + QString getPassword(bool quiet = false); + QSharedPointer getConfirmedPassword(); int clipText(const QString& text); QSharedPointer unlockDatabase(const QString& databaseFilename, const bool isPasswordProtected = true, const QString& keyFilename = {}, const QString& yubiKeySlot = {}, - FILE* outputDescriptor = STDOUT, - FILE* errorDescriptor = STDERR); + bool quiet = false); QStringList splitCommandString(const QString& command); @@ -59,11 +60,6 @@ namespace Utils * (case-insensitive). */ QStringList findAttributes(const EntryAttributes& attributes, const QString& name); - - namespace Test - { - void setNextPassword(const QString& password, bool repeat = false); - } }; // namespace Utils #endif // KEEPASSXC_UTILS_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 85351e61d..a9b276fda 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -118,13 +118,14 @@ private: void enterInteractiveMode(const QStringList& arguments) { + auto& err = Utils::STDERR; // Replace command list with interactive version Commands::setupCommands(true); - Open o; + Open openCmd; QStringList openArgs(arguments); openArgs.removeFirst(); - o.execute(openArgs); + openCmd.execute(openArgs); QScopedPointer reader; #if defined(USE_READLINE) @@ -133,12 +134,10 @@ void enterInteractiveMode(const QStringList& arguments) reader.reset(new SimpleLineReader()); #endif - QSharedPointer currentDatabase(o.currentDatabase); + QSharedPointer currentDatabase(openCmd.currentDatabase); QString command; while (true) { - TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); - QString prompt; if (currentDatabase) { prompt += currentDatabase->metadata()->name(); @@ -159,7 +158,7 @@ void enterInteractiveMode(const QStringList& arguments) auto cmd = Commands::getCommand(args[0]); if (!cmd) { - errorTextStream << QObject::tr("Unknown command %1").arg(args[0]) << "\n"; + err << QObject::tr("Unknown command %1").arg(args[0]) << "\n"; continue; } else if (cmd->name == "quit" || cmd->name == "exit") { break; @@ -186,10 +185,12 @@ int main(int argc, char** argv) QCoreApplication::setApplicationVersion(KEEPASSXC_VERSION); Bootstrap::bootstrap(); + Utils::setDefaultTextStreams(); Commands::setupCommands(false); - TextStream out(stdout); - TextStream err(stderr); + auto& out = Utils::STDOUT; + auto& err = Utils::STDERR; + QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << QString(argv[i]); diff --git a/src/format/KdbxWriter.h b/src/format/KdbxWriter.h index 6d759bad2..d5e214a51 100644 --- a/src/format/KdbxWriter.h +++ b/src/format/KdbxWriter.h @@ -77,8 +77,7 @@ protected: { Q_ASSERT(static_cast(data.size()) < (1ull << (sizeof(SizedQInt) * 8))); - QByteArray fieldIdArr; - fieldIdArr[0] = static_cast(fieldId); + QByteArray fieldIdArr(1, static_cast(fieldId)); CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); CHECK_RETURN_FALSE(writeData( device, Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); diff --git a/src/keys/YkChallengeResponseKeyCLI.cpp b/src/keys/YkChallengeResponseKeyCLI.cpp index c218f1f55..48420fb8c 100644 --- a/src/keys/YkChallengeResponseKeyCLI.cpp +++ b/src/keys/YkChallengeResponseKeyCLI.cpp @@ -30,13 +30,13 @@ QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d"); YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(int slot, bool blocking, QString messageInteraction, - FILE* outputDescriptor) + QIODevice* out) : ChallengeResponseKey(UUID) , m_slot(slot) , m_blocking(blocking) , m_messageInteraction(messageInteraction) - , m_out(outputDescriptor) { + m_out.setDevice(out); } QByteArray YkChallengeResponseKeyCLI::rawKey() const @@ -54,12 +54,11 @@ bool YkChallengeResponseKeyCLI::challenge(const QByteArray& c) bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge, unsigned int retries) { - QTextStream out(m_out, QIODevice::WriteOnly); do { --retries; if (m_blocking) { - out << m_messageInteraction << endl; + m_out << m_messageInteraction << endl; } YubiKey::ChallengeResult result = YubiKey::instance()->challenge(m_slot, m_blocking, challenge, m_key); if (result == YubiKey::SUCCESS) { diff --git a/src/keys/YkChallengeResponseKeyCLI.h b/src/keys/YkChallengeResponseKeyCLI.h index ed2d62b2a..e1e042d16 100644 --- a/src/keys/YkChallengeResponseKeyCLI.h +++ b/src/keys/YkChallengeResponseKeyCLI.h @@ -23,6 +23,7 @@ #include "keys/drivers/YubiKey.h" #include +#include #include class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey @@ -32,7 +33,7 @@ class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey public: static QUuid UUID; - explicit YkChallengeResponseKeyCLI(int slot, bool blocking, QString messageInteraction, FILE* outputDescriptor); + explicit YkChallengeResponseKeyCLI(int slot, bool blocking, QString messageInteraction, QIODevice* out); QByteArray rawKey() const override; bool challenge(const QByteArray& challenge) override; @@ -43,7 +44,7 @@ private: int m_slot; bool m_blocking; QString m_messageInteraction; - FILE* m_out; + QTextStream m_out; }; #endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 23b7a007e..10bde7a08 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -23,12 +23,6 @@ #include "core/Global.h" #include "core/Tools.h" #include "crypto/Crypto.h" -#include "format/Kdbx3Reader.h" -#include "format/Kdbx3Writer.h" -#include "format/Kdbx4Reader.h" -#include "format/Kdbx4Writer.h" -#include "format/KdbxXmlReader.h" -#include "format/KeePass2.h" #include "keys/drivers/YubiKey.h" #include "cli/Add.h" @@ -56,14 +50,9 @@ #include "cli/Utils.h" #include -#include #include -#include -#include #include -#include - QTEST_MAIN(TestCli) QSharedPointer globalCurrentDatabase; @@ -75,85 +64,48 @@ void TestCli::initTestCase() 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(); - - // Load the NewDatabase2.kdbx file into temporary storage - QFile sourceDbFile2(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase2.kdbx")); - QVERIFY(sourceDbFile2.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile2, m_dbData2)); - sourceDbFile2.close(); - - // Load the KeyFileProtected.kdbx file into temporary storage - QFile sourceDbFile3(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtected.kdbx")); - QVERIFY(sourceDbFile3.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile3, m_keyFileProtectedDbData)); - sourceDbFile3.close(); - - // Load the KeyFileProtectedNoPassword.kdbx file into temporary storage - QFile sourceDbFile4(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtectedNoPassword.kdbx")); - QVERIFY(sourceDbFile4.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFile4, m_keyFileProtectedNoPasswordDbData)); - sourceDbFile4.close(); - - QFile sourceDbFileYubiKeyProtected(QString(KEEPASSX_TEST_DATA_DIR).append("/YubiKeyProtectedPasswords.kdbx")); - QVERIFY(sourceDbFileYubiKeyProtected.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceDbFileYubiKeyProtected, m_yubiKeyProtectedDbData)); - sourceDbFileYubiKeyProtected.close(); - - // Load the NewDatabase.xml file into temporary storage - QFile sourceXmlFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml")); - QVERIFY(sourceXmlFile.open(QIODevice::ReadOnly)); - QVERIFY(Tools::readAllFromDevice(&sourceXmlFile, m_xmlData)); - sourceXmlFile.close(); + auto fd = new QFile(); +#ifdef Q_OS_WIN + fd->open(fopen("nul", "w"), QIODevice::WriteOnly); +#else + fd->open(fopen("/dev/null", "w"), QIODevice::WriteOnly); +#endif + Utils::DEVNULL.setDevice(fd); } void TestCli::init() { + const auto file = QString(KEEPASSX_TEST_DATA_DIR).append("/%1"); + m_dbFile.reset(new TemporaryFile()); - m_dbFile->open(); - m_dbFile->write(m_dbData); - m_dbFile->close(); + m_dbFile->copyFromFile(file.arg("NewDatabase.kdbx")); m_dbFile2.reset(new TemporaryFile()); - m_dbFile2->open(); - m_dbFile2->write(m_dbData2); - m_dbFile2->close(); + m_dbFile2->copyFromFile(file.arg("NewDatabase2.kdbx")); m_xmlFile.reset(new TemporaryFile()); - m_xmlFile->open(); - m_xmlFile->write(m_xmlData); - m_xmlFile->close(); + m_xmlFile->copyFromFile(file.arg("NewDatabase.xml")); m_keyFileProtectedDbFile.reset(new TemporaryFile()); - m_keyFileProtectedDbFile->open(); - m_keyFileProtectedDbFile->write(m_keyFileProtectedDbData); - m_keyFileProtectedDbFile->close(); + m_keyFileProtectedDbFile->copyFromFile(file.arg("KeyFileProtected.kdbx")); m_keyFileProtectedNoPasswordDbFile.reset(new TemporaryFile()); - m_keyFileProtectedNoPasswordDbFile->open(); - m_keyFileProtectedNoPasswordDbFile->write(m_keyFileProtectedNoPasswordDbData); - m_keyFileProtectedNoPasswordDbFile->close(); + m_keyFileProtectedNoPasswordDbFile->copyFromFile(file.arg("KeyFileProtectedNoPassword.kdbx")); m_yubiKeyProtectedDbFile.reset(new TemporaryFile()); - m_yubiKeyProtectedDbFile->open(); - m_yubiKeyProtectedDbFile->write(m_yubiKeyProtectedDbData); - m_yubiKeyProtectedDbFile->close(); + m_yubiKeyProtectedDbFile->copyFromFile(file.arg("YubiKeyProtectedPasswords.kdbx")); - m_stdinFile.reset(new TemporaryFile()); - m_stdinFile->open(); - Utils::STDIN = fdopen(m_stdinFile->handle(), "r+"); + m_stdout.reset(new QBuffer()); + m_stdout->open(QIODevice::ReadWrite); + Utils::STDOUT.setDevice(m_stdout.data()); - m_stdoutFile.reset(new TemporaryFile()); - m_stdoutFile->open(); - Utils::STDOUT = fdopen(m_stdoutFile->handle(), "r+"); + m_stderr.reset(new QBuffer()); + m_stderr->open(QIODevice::ReadWrite); + Utils::STDERR.setDevice(m_stderr.data()); - m_stderrFile.reset(new TemporaryFile()); - m_stderrFile->open(); - Utils::STDERR = fdopen(m_stderrFile->handle(), "r+"); + m_stdin.reset(new QBuffer()); + m_stdin->open(QIODevice::ReadWrite); + Utils::STDIN.setDevice(m_stdin.data()); } void TestCli::cleanup() @@ -164,28 +116,91 @@ void TestCli::cleanup() m_keyFileProtectedNoPasswordDbFile.reset(); m_yubiKeyProtectedDbFile.reset(); - m_stdinFile.reset(); - Utils::STDIN = stdin; - - m_stdoutFile.reset(); - Utils::STDOUT = stdout; - - m_stderrFile.reset(); - Utils::STDERR = stderr; + Utils::STDOUT.setDevice(nullptr); + Utils::STDERR.setDevice(nullptr); + Utils::STDIN.setDevice(nullptr); } -void TestCli::cleanupTestCase() +QSharedPointer TestCli::readDatabase(const QString& filename, const QString& pw, const QString& keyfile) { -} + auto db = QSharedPointer::create(); + auto key = QSharedPointer::create(); + + if (filename.isEmpty()) { + // Open the default test database + key->addKey(QSharedPointer::create("a")); + if (!db->open(m_dbFile->fileName(), key)) { + return {}; + } + } else { + // Open the specified database file using supplied credentials + key->addKey(QSharedPointer::create(pw)); + if (!keyfile.isEmpty()) { + auto filekey = QSharedPointer::create(); + filekey->load(keyfile); + key->addKey(filekey); + } + + if (!db->open(filename, key)) { + return {}; + } + } -QSharedPointer TestCli::readTestDatabase() const -{ - Utils::Test::setNextPassword("a"); - auto db = QSharedPointer(Utils::unlockDatabase(m_dbFile->fileName(), true, "", "", Utils::STDOUT)); - m_stdoutFile->seek(ftell(Utils::STDOUT)); // re-synchronize handles return db; } +int TestCli::execCmd(Command& cmd, const QStringList& args) const +{ + // Move to end of stream + m_stdout->readAll(); + m_stderr->readAll(); + + // Record stream position + auto outPos = m_stdout->pos(); + auto errPos = m_stderr->pos(); + + // Execute command + int ret = cmd.execute(args); + + // Move back to recorded position + m_stdout->seek(outPos); + m_stderr->seek(errPos); + + // Skip over blank lines + QByteArray newline("\n"); + while (m_stdout->peek(1) == newline) { + m_stdout->readLine(); + } + while (m_stderr->peek(1) == newline) { + m_stdout->readLine(); + } + + return ret; +} + +bool TestCli::isTotp(const QString& value) +{ + static const QRegularExpression totp("^\\d{6}$"); + return totp.match(value.trimmed()).hasMatch(); +} + +void TestCli::setInput(const QString& input) +{ + setInput(QStringList(input)); +} + +void TestCli::setInput(const QStringList& input) +{ + auto ba = input.join("\n").toLatin1(); + // Always end in newline + if (!ba.endsWith("\n")) { + ba.append("\n"); + } + auto pos = m_stdin->pos(); + m_stdin->write(ba); + m_stdin->seek(pos); +} + void TestCli::testBatchCommands() { Commands::setupCommands(false); @@ -250,24 +265,23 @@ void TestCli::testAdd() QVERIFY(!addCmd.name.isEmpty()); QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name)); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", - "-u", - "newuser", - "--url", - "https://example.com/", - "-g", - "-L", - "20", - m_dbFile->fileName(), - "/newuser-entry"}); - m_stderrFile->reset(); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry.\n")); + setInput("a"); + execCmd(addCmd, + {"add", + "-u", + "newuser", + "--url", + "https://example.com/", + "-g", + "-L", + "20", + m_dbFile->fileName(), + "/newuser-entry"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry.")); - auto db = readTestDatabase(); + auto db = readDatabase(); auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -275,33 +289,21 @@ void TestCli::testAdd() QCOMPARE(entry->password().size(), 20); // Quiet option - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", "-q", "-u", "newuser", "-g", "-L", "20", m_dbFile->fileName(), "/newentry-quiet"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - db = readTestDatabase(); + setInput("a"); + execCmd(addCmd, {"add", "-q", "-u", "newuser", "-g", "-L", "20", m_dbFile->fileName(), "/newentry-quiet"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newentry-quiet"); QVERIFY(entry); QCOMPARE(entry->password().size(), 20); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("newpassword"); - addCmd.execute( - {"add", "-u", "newuser2", "--url", "https://example.net/", "-p", m_dbFile->fileName(), "/newuser-entry2"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - m_stdoutFile->readLine(); // skip password input - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry2.\n")); + setInput({"a", "newpassword"}); + execCmd(addCmd, + {"add", "-u", "newuser2", "--url", "https://example.net/", "-p", m_dbFile->fileName(), "/newuser-entry2"}); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry2.")); - db = readTestDatabase(); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newuser-entry2"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser2")); @@ -309,17 +311,11 @@ void TestCli::testAdd() QCOMPARE(entry->password(), QString("newpassword")); // Password generation options - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", "-u", "newuser3", "-g", "-L", "34", m_dbFile->fileName(), "/newuser-entry3"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry3.\n")); + setInput("a"); + execCmd(addCmd, {"add", "-u", "newuser3", "-g", "-L", "34", m_dbFile->fileName(), "/newuser-entry3"}); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry3.")); - db = readTestDatabase(); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newuser-entry3"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser3")); @@ -327,29 +323,24 @@ void TestCli::testAdd() QRegularExpression defaultPasswordClassesRegex("^[a-zA-Z0-9]+$"); QVERIFY(defaultPasswordClassesRegex.match(entry->password()).hasMatch()); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addCmd.execute({"add", - "-u", - "newuser4", - "-g", - "-L", - "20", - "--every-group", - "-s", - "-n", - "-U", - "-l", - m_dbFile->fileName(), - "/newuser-entry4"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added entry newuser-entry4.\n")); + setInput("a"); + execCmd(addCmd, + {"add", + "-u", + "newuser4", + "-g", + "-L", + "20", + "--every-group", + "-s", + "-n", + "-U", + "-l", + m_dbFile->fileName(), + "/newuser-entry4"}); + QVERIFY(m_stdout->readAll().contains("Successfully added entry newuser-entry4.")); - db = readTestDatabase(); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newuser-entry4"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser4")); @@ -363,81 +354,44 @@ void TestCli::testAddGroup() QVERIFY(!addGroupCmd.name.isEmpty()); QVERIFY(addGroupCmd.getDescriptionLine().contains(addGroupCmd.name)); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group"}); - m_stderrFile->reset(); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added group new_group.\n")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/new_group"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully added group new_group.\n")); - auto db = readTestDatabase(); + auto db = readDatabase(); auto* group = db->rootGroup()->findGroupByPath("new_group"); QVERIFY(group); QCOMPARE(group->name(), QString("new_group")); // Trying to add the same group should fail. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group /new_group already exists!\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/new_group"}); + QVERIFY(m_stderr->readAll().contains("Group /new_group already exists!")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should be able to add groups down the tree. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/new_group/newer_group"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully added group newer_group.\n")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/new_group/newer_group"}); + QVERIFY(m_stdout->readAll().contains("Successfully added group newer_group.")); - db = readTestDatabase(); + db = readDatabase(); group = db->rootGroup()->findGroupByPath("new_group/newer_group"); QVERIFY(group); QCOMPARE(group->name(), QString("newer_group")); // Should fail if the path is invalid. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/invalid_group/newer_group"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group /invalid_group not found.\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/invalid_group/newer_group"}); + QVERIFY(m_stderr->readAll().contains("Group /invalid_group not found.")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should fail to add the root group. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - addGroupCmd.execute({"mkdir", m_dbFile->fileName(), "/"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group / already exists!\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); -} - -bool isTOTP(const QString& value) -{ - QString val = value.trimmed(); - if (val.length() < 5 || val.length() > 6) { - return false; - } - for (int i = 0; i < val.length(); ++i) { - if (!value[i].isDigit()) { - return false; - } - } - return true; + setInput("a"); + execCmd(addGroupCmd, {"mkdir", m_dbFile->fileName(), "/"}); + QVERIFY(m_stderr->readAll().contains("Group / already exists!")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testAnalyze() @@ -448,12 +402,13 @@ void TestCli::testAnalyze() const QString hibpPath = QString(KEEPASSX_TEST_DATA_DIR).append("/hibp.txt"); - Utils::Test::setNextPassword("a"); - analyzeCmd.execute({"analyze", "--hibp", hibpPath, m_dbFile->fileName()}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - auto output = m_stdoutFile->readAll(); - QVERIFY(output.contains("Sample Entry") && output.contains("123")); + setInput("a"); + execCmd(analyzeCmd, {"analyze", "--hibp", hibpPath, m_dbFile->fileName()}); + auto output = m_stdout->readAll(); + QVERIFY(output.contains("Sample Entry")); + QVERIFY(output.contains("123")); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); } void TestCli::testClip() @@ -466,12 +421,9 @@ void TestCli::testClip() QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name)); // Password - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"}); - - m_stderrFile->reset(); - m_stdoutFile->reset(); - QString errorOutput(m_stderrFile->readAll()); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry"}); + QString errorOutput(m_stderr->readAll()); if (QProcessEnvironment::systemEnvironment().contains("WAYLAND_DISPLAY")) { QSKIP("Clip test skipped due to QClipboard and Wayland issues"); @@ -482,32 +434,31 @@ void TestCli::testClip() QSKIP("Clip test skipped due to missing clipboard tool"); } + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); QTRY_COMPARE(clipboard->text(), QString("Password")); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Entry's \"Password\" attribute copied to the clipboard!\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Entry's \"Password\" attribute copied to the clipboard!\n")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry", "-q"}); - m_stdoutFile->seek(pos); - // Output should be empty when quiet option is set. - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "-q"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); QTRY_COMPARE(clipboard->text(), QString("Password")); // Username - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry", "-a", "username"}); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "-a", "username"}); QTRY_COMPARE(clipboard->text(), QString("User Name")); // TOTP - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry", "--totp"}); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "--totp"}); - QTRY_VERIFY(isTOTP(clipboard->text())); + QTRY_VERIFY(isTotp(clipboard->text())); // Password with timeout - Utils::Test::setNextPassword("a"); + setInput("a"); // clang-format off QFuture future = QtConcurrent::run(&clipCmd, static_cast(&DatabaseCommand::execute), @@ -520,47 +471,36 @@ void TestCli::testClip() future.waitForFinished(); // TOTP with timeout - Utils::Test::setNextPassword("a"); + setInput("a"); future = QtConcurrent::run(&clipCmd, static_cast(&DatabaseCommand::execute), QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1", "-t"}); - QTRY_VERIFY(isTOTP(clipboard->text())); + QTRY_VERIFY(isTotp(clipboard->text())); QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 2000); future.waitForFinished(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "0"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid timeout value 0.\n")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "0"}); + QVERIFY(m_stderr->readAll().contains("Invalid timeout value 0.\n")); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "bleuh"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid timeout value bleuh.\n")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "--totp", "/Sample Entry", "bleuh"}); + QVERIFY(m_stderr->readAll().contains("Invalid timeout value bleuh.\n")); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry with path /Sample Entry has no TOTP set up.\n")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); + QVERIFY(m_stderr->readAll().contains("Entry with path /Sample Entry has no TOTP set up.\n")); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"}); - m_stderrFile->seek(posErr); - QCOMPARE( - m_stderrFile->readAll(), - QByteArray("ERROR: attribute TESTAttribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"}); + QVERIFY(m_stderr->readAll().contains( + "ERROR: attribute TESTAttribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - clipCmd.execute({"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("ERROR: Please specify one of --attribute or --totp, not both.\n")); + setInput("a"); + execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"}); + QVERIFY(m_stderr->readAll().contains("ERROR: Please specify one of --attribute or --totp, not both.\n")); } void TestCli::testCreate() @@ -574,165 +514,110 @@ void TestCli::testCreate() // Testing password option, password mismatch dbFilename = testDir->path() + "/testCreate_pw.kdbx"; - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("b"); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stderrFile->reset(); - m_stdoutFile->reset(); + setInput({"a", "b"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Error: Passwords do not match.\n")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Failed to set database password.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Error: Passwords do not match.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Failed to set database password.\n")); // Testing password option - Utils::Test::setNextPassword("a", true); - qint64 pos = m_stdoutFile->pos(); - qint64 errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); - Utils::Test::setNextPassword("a"); - auto db = Utils::unlockDatabase(dbFilename, true, "", "", Utils::DEVNULL); + auto db = readDatabase(dbFilename, "a"); QVERIFY(db); // Testing with empty password (deny it) dbFilename = testDir->path() + "/testCreate_blankpw.kdbx"; - Utils::Test::setNextPassword(""); - m_stdinFile->reset(); - m_stdinFile->write("n\n"); - m_stdinFile->reset(); - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + setInput({"", "n"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QVERIFY(m_stdoutFile->readLine().contains("empty password")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Failed to set database password.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QVERIFY(m_stderr->readLine().contains("empty password")); + QCOMPARE(m_stderr->readLine(), QByteArray("Failed to set database password.\n")); // Testing with empty password (accept it) - Utils::Test::setNextPassword(""); - m_stdinFile->reset(); - m_stdinFile->write("y\n"); - m_stdinFile->reset(); - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + setInput({"", "y"}); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QVERIFY(m_stdoutFile->readLine().contains("empty password")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QVERIFY(m_stderr->readLine().contains("empty password")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); - Utils::Test::setNextPassword(""); - db = Utils::unlockDatabase(dbFilename, true, "", "", Utils::DEVNULL); + db = readDatabase(dbFilename, ""); QVERIFY(db); // Should refuse to create the database if it already exists. - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", "-p", dbFilename}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(createCmd, {"db-create", dbFilename, "-p"}); // Output should be empty when there is an error. - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray()); QString errorMessage = QString("File " + dbFilename + " already exists.\n"); - QCOMPARE(m_stderrFile->readAll(), errorMessage.toUtf8()); + QCOMPARE(m_stderr->readAll(), errorMessage.toUtf8()); // Should refuse to create without any key provided. dbFilename = testDir->path() + "/testCreate_key.kdbx"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("No key is set. Aborting database creation.\n")); + execCmd(createCmd, {"db-create", dbFilename}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QCOMPARE(m_stderr->readLine(), QByteArray("No key is set. Aborting database creation.\n")); // Testing with keyfile creation dbFilename = testDir->path() + "/testCreate_key2.kdbx"; QString keyfilePath = testDir->path() + "/keyfile.txt"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a", true); - createCmd.execute({"db-create", dbFilename, "-p", "-k", keyfilePath}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-k", keyfilePath}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); - Utils::Test::setNextPassword("a"); - db = Utils::unlockDatabase(dbFilename, true, keyfilePath, "", Utils::DEVNULL); + db = readDatabase(dbFilename, "a", keyfilePath); QVERIFY(db); // Testing with existing keyfile dbFilename = testDir->path() + "/testCreate_key3.kdbx"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a", true); - createCmd.execute({"db-create", dbFilename, "-p", "-k", keyfilePath}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-k", keyfilePath}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully created new database.\n")); - Utils::Test::setNextPassword("a"); - db = Utils::unlockDatabase(dbFilename, true, keyfilePath, "", Utils::DEVNULL); + db = readDatabase(dbFilename, "a", keyfilePath); QVERIFY(db); // Invalid decryption time (format). dbFilename = testDir->path() + "/testCreate_time.kdbx"; - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p", "-t", "NAN"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-t", "NAN"}); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid decryption time NAN.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QCOMPARE(m_stderr->readAll(), QByteArray("Invalid decryption time NAN.\n")); // Invalid decryption time (range). - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - createCmd.execute({"db-create", dbFilename, "-p", "-t", "10"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-t", "10"}); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QVERIFY(m_stderrFile->readAll().contains(QByteArray("Target decryption time must be between"))); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains(QByteArray("Target decryption time must be between"))); int encryptionTime = 500; // Custom encryption time - pos = m_stdoutFile->pos(); - errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a", true); + setInput({"a", "a"}); int epochBefore = QDateTime::currentMSecsSinceEpoch(); - createCmd.execute({"db-create", dbFilename, "-p", "-t", QString::number(encryptionTime)}); + execCmd(createCmd, {"db-create", dbFilename, "-p", "-t", QString::number(encryptionTime)}); // Removing 100ms to make sure we account for changes in computation time. QVERIFY(QDateTime::currentMSecsSinceEpoch() > (epochBefore + encryptionTime - 100)); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Benchmarking key derivation function for 500ms delay.\n")); - QVERIFY(m_stdoutFile->readLine().contains(QByteArray("rounds for key derivation function.\n"))); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Benchmarking key derivation function for 500ms delay.\n")); + QVERIFY(m_stdout->readLine().contains(QByteArray("rounds for key derivation function.\n"))); - Utils::Test::setNextPassword("a"); - db = Utils::unlockDatabase(dbFilename, true, "", "", Utils::DEVNULL); + db = readDatabase(dbFilename, "a"); QVERIFY(db); } @@ -742,33 +627,27 @@ void TestCli::testInfo() QVERIFY(!infoCmd.name.isEmpty()); QVERIFY(infoCmd.getDescriptionLine().contains(infoCmd.name)); - Utils::Test::setNextPassword("a"); - infoCmd.execute({"db-info", m_dbFile->fileName()}); - m_stdoutFile->reset(); - m_stderrFile->reset(); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QVERIFY(m_stdoutFile->readLine().contains(QByteArray("UUID: "))); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Name: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Description: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Cipher: AES 256-bit\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Recycle bin is enabled.\n")); + setInput("a"); + execCmd(infoCmd, {"db-info", m_dbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QVERIFY(m_stdout->readLine().contains(QByteArray("UUID: "))); + QCOMPARE(m_stdout->readLine(), QByteArray("Name: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Description: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Cipher: AES 256-bit\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Recycle bin is enabled.\n")); // Test with quiet option. - qint64 pos = m_stdoutFile->pos(); - qint64 errPos = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - infoCmd.execute({"db-info", "-q", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QVERIFY(m_stdoutFile->readLine().contains(QByteArray("UUID: "))); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Name: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Description: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Cipher: AES 256-bit\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Recycle bin is enabled.\n")); + setInput("a"); + execCmd(infoCmd, {"db-info", "-q", m_dbFile->fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QVERIFY(m_stdout->readLine().contains(QByteArray("UUID: "))); + QCOMPARE(m_stdout->readLine(), QByteArray("Name: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Description: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Cipher: AES 256-bit\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("KDF: AES (6000 rounds)\n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Recycle bin is enabled.\n")); } void TestCli::testDiceware() @@ -777,33 +656,25 @@ void TestCli::testDiceware() QVERIFY(!dicewareCmd.name.isEmpty()); QVERIFY(dicewareCmd.getDescriptionLine().contains(dicewareCmd.name)); - dicewareCmd.execute({"diceware"}); - m_stdoutFile->reset(); - QString passphrase(m_stdoutFile->readLine()); + execCmd(dicewareCmd, {"diceware"}); + QString passphrase(m_stdout->readLine()); QVERIFY(!passphrase.isEmpty()); - dicewareCmd.execute({"diceware", "-W", "2"}); - m_stdoutFile->seek(passphrase.toLatin1().size()); - passphrase = m_stdoutFile->readLine(); + execCmd(dicewareCmd, {"diceware", "-W", "2"}); + passphrase = m_stdout->readLine(); QCOMPARE(passphrase.split(" ").size(), 2); - auto pos = m_stdoutFile->pos(); - dicewareCmd.execute({"diceware", "-W", "10"}); - m_stdoutFile->seek(pos); - passphrase = m_stdoutFile->readLine(); + execCmd(dicewareCmd, {"diceware", "-W", "10"}); + passphrase = m_stdout->readLine(); QCOMPARE(passphrase.split(" ").size(), 10); // Testing with invalid word count - auto posErr = m_stderrFile->pos(); - dicewareCmd.execute({"diceware", "-W", "-10"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid word count -10\n")); + execCmd(dicewareCmd, {"diceware", "-W", "-10"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid word count -10\n")); // Testing with invalid word count format - posErr = m_stderrFile->pos(); - dicewareCmd.execute({"diceware", "-W", "bleuh"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid word count bleuh\n")); + execCmd(dicewareCmd, {"diceware", "-W", "bleuh"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid word count bleuh\n")); TemporaryFile wordFile; wordFile.open(); @@ -812,10 +683,8 @@ void TestCli::testDiceware() } wordFile.close(); - pos = m_stdoutFile->pos(); - dicewareCmd.execute({"diceware", "-W", "11", "-w", wordFile.fileName()}); - m_stdoutFile->seek(pos); - passphrase = m_stdoutFile->readLine(); + execCmd(dicewareCmd, {"diceware", "-W", "11", "-w", wordFile.fileName()}); + passphrase = m_stdout->readLine(); const auto words = passphrase.split(" "); QCOMPARE(words.size(), 11); QRegularExpression regex("^word\\d+$"); @@ -830,10 +699,8 @@ void TestCli::testDiceware() } smallWordFile.close(); - posErr = m_stderrFile->pos(); - dicewareCmd.execute({"diceware", "-W", "11", "-w", smallWordFile.fileName()}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("The word list is too small (< 1000 items)\n")); + execCmd(dicewareCmd, {"diceware", "-W", "11", "-w", smallWordFile.fileName()}); + QCOMPARE(m_stderr->readLine(), QByteArray("The word list is too small (< 1000 items)\n")); } void TestCli::testEdit() @@ -842,15 +709,20 @@ void TestCli::testEdit() QVERIFY(!editCmd.name.isEmpty()); QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name)); - Utils::Test::setNextPassword("a"); - // clang-format off - editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"}); - // clang-format on - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully edited entry newtitle.\n")); + setInput("a"); + execCmd(editCmd, + {"edit", + "-u", + "newuser", + "--url", + "https://otherurl.example.com/", + "-t", + "newtitle", + m_dbFile->fileName(), + "/Sample Entry"}); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully edited entry newtitle.\n")); - auto db = readTestDatabase(); + auto db = readDatabase(); auto* entry = db->rootGroup()->findEntryByPath("/newtitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -858,18 +730,14 @@ void TestCli::testEdit() QCOMPARE(entry->password(), QString("Password")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", m_dbFile->fileName(), "-q", "-t", "newertitle", "/newtitle"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(editCmd, {"edit", m_dbFile->fileName(), "-q", "-t", "newertitle", "/newtitle"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newertitle"}); - db = readTestDatabase(); + setInput("a"); + execCmd(editCmd, {"edit", "-g", m_dbFile->fileName(), "/newertitle"}); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/newertitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -877,9 +745,9 @@ void TestCli::testEdit() QVERIFY(!entry->password().isEmpty()); QVERIFY(entry->password() != QString("Password")); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", "-g", "-L", "34", "-t", "evennewertitle", m_dbFile->fileName(), "/newertitle"}); - db = readTestDatabase(); + setInput("a"); + execCmd(editCmd, {"edit", "-g", "-L", "34", "-t", "evennewertitle", m_dbFile->fileName(), "/newertitle"}); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->username(), QString("newuser")); @@ -889,36 +757,31 @@ void TestCli::testEdit() QRegularExpression defaultPasswordClassesRegex("^[a-zA-Z0-9]+$"); QVERIFY(defaultPasswordClassesRegex.match(entry->password()).hasMatch()); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - editCmd.execute({"edit", - "-g", - "-L", - "20", - "--every-group", - "-s", - "-n", - "--upper", - "-l", - m_dbFile->fileName(), - "/evennewertitle"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully edited entry evennewertitle.\n")); + setInput("a"); + execCmd(editCmd, + {"edit", + "-g", + "-L", + "20", + "--every-group", + "-s", + "-n", + "--upper", + "-l", + m_dbFile->fileName(), + "/evennewertitle"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully edited entry evennewertitle.\n")); - db = readTestDatabase(); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->password().size(), 20); QVERIFY(!defaultPasswordClassesRegex.match(entry->password()).hasMatch()); - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("newpassword"); - editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/evennewertitle"}); - db = readTestDatabase(); + setInput({"a", "newpassword"}); + execCmd(editCmd, {"edit", "-p", m_dbFile->fileName(), "/evennewertitle"}); + db = readDatabase(); + QVERIFY(db); entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->password(), QString("newpassword")); @@ -1014,14 +877,9 @@ void TestCli::testEstimate() QVERIFY(!estimateCmd.name.isEmpty()); QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name)); - QTextStream in(m_stdinFile.data()); - QTextStream out(m_stdoutFile.data()); - - in << input << endl; - in.seek(0); - estimateCmd.execute({"estimate", "-a"}); - out.seek(0); - auto result = out.readAll(); + setInput(input); + execCmd(estimateCmd, {"estimate", "-a"}); + auto result = QString(m_stdout->readAll()); QVERIFY(result.contains("Length " + length)); QVERIFY(result.contains("Entropy " + entropy)); QVERIFY(result.contains("Log10 " + log10)); @@ -1036,61 +894,47 @@ void TestCli::testExport() QVERIFY(!exportCmd.name.isEmpty()); QVERIFY(exportCmd.getDescriptionLine().contains(exportCmd.name)); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", m_dbFile->fileName()}); + setInput("a"); + execCmd(exportCmd, {"export", m_dbFile->fileName()}); - m_stdoutFile->seek(0); - m_stdoutFile->readLine(); // skip prompt line + TemporaryFile xmlOutput; + xmlOutput.open(QIODevice::WriteOnly); + xmlOutput.write(m_stdout->readAll()); + xmlOutput.close(); - KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1); QScopedPointer db(new Database()); - reader.readDatabase(m_stdoutFile.data(), db.data()); - QVERIFY(!reader.hasError()); - QVERIFY(db.data()); + QVERIFY(db->import(xmlOutput.fileName())); + auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry"); QVERIFY(entry); QCOMPARE(entry->password(), QString("Password")); - m_stdoutFile->reset(); - // Quiet option QScopedPointer dbQuiet(new Database()); - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", "-f", "xml", "-q", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - reader.readDatabase(m_stdoutFile.data(), dbQuiet.data()); - QVERIFY(!reader.hasError()); - QVERIFY(db.data()); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(exportCmd, {"export", "-f", "xml", "-q", m_dbFile->fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + + xmlOutput.open(QIODevice::WriteOnly); + xmlOutput.write(m_stdout->readAll()); + xmlOutput.close(); + + QVERIFY(db->import(xmlOutput.fileName())); // CSV exporting - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", "-f", "csv", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip prompt line - m_stderrFile->seek(posErr); - QByteArray csvHeader = m_stdoutFile->readLine(); + setInput("a"); + execCmd(exportCmd, {"export", "-f", "csv", m_dbFile->fileName()}); + QByteArray csvHeader = m_stdout->readLine(); QCOMPARE(csvHeader, QByteArray("\"Group\",\"Title\",\"Username\",\"Password\",\"URL\",\"Notes\"\n")); - QByteArray csvData = m_stdoutFile->readAll(); + QByteArray csvData = m_stdout->readAll(); QVERIFY(csvData.contains(QByteArray( "\"NewDatabase\",\"Sample Entry\",\"User Name\",\"Password\",\"http://www.somesite.com/\",\"Notes\"\n"))); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); // test invalid format - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - exportCmd.execute({"export", "-f", "yaml", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip prompt line - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Unsupported format yaml\n")); + setInput("a"); + execCmd(exportCmd, {"export", "-f", "yaml", m_dbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Unsupported format yaml\n")); } void TestCli::testGenerate_data() @@ -1130,35 +974,30 @@ void TestCli::testGenerate() QVERIFY(!generateCmd.name.isEmpty()); QVERIFY(generateCmd.getDescriptionLine().contains(generateCmd.name)); - qint64 pos = 0; - // run multiple times to make accidental passes unlikely - TextStream stream(m_stdoutFile.data()); for (int i = 0; i < 10; ++i) { - generateCmd.execute(parameters); - stream.seek(pos); + execCmd(generateCmd, parameters); QRegularExpression regex(pattern); - QString password = stream.readLine(); - pos = stream.pos(); +#ifdef Q_OS_UNIX + QString password = QString::fromUtf8(m_stdout->readLine()); +#else + QString password = QString::fromLatin1(m_stdout->readLine()); +#endif + QVERIFY2(regex.match(password).hasMatch(), qPrintable("Password " + password + " does not match pattern " + pattern)); + QCOMPARE(m_stderr->readAll(), QByteArray()); } // Testing with invalid password length - auto posErr = m_stderrFile->pos(); - generateCmd.execute({"generate", "-L", "-10"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid password length -10\n")); + execCmd(generateCmd, {"generate", "-L", "-10"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid password length -10\n")); - posErr = m_stderrFile->pos(); - generateCmd.execute({"generate", "-L", "0"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid password length 0\n")); + execCmd(generateCmd, {"generate", "-L", "0"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid password length 0\n")); // Testing with invalid word count format - posErr = m_stderrFile->pos(); - generateCmd.execute({"generate", "-L", "bleuh"}); - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Invalid password length bleuh\n")); + execCmd(generateCmd, {"generate", "-L", "bleuh"}); + QCOMPARE(m_stderr->readLine(), QByteArray("Invalid password length bleuh\n")); } void TestCli::testImport() @@ -1168,52 +1007,41 @@ void TestCli::testImport() QVERIFY(importCmd.getDescriptionLine().contains(importCmd.name)); QScopedPointer testDir(new QTemporaryDir()); - QString databaseFilename = testDir->path() + "testImport1.kdbx"; + QString databaseFilename = testDir->path() + "/testImport1.kdbx"; - Utils::Test::setNextPassword("a", true); - importCmd.execute({"import", m_xmlFile->fileName(), databaseFilename}); + setInput({"a", "a"}); + execCmd(importCmd, {"import", m_xmlFile->fileName(), databaseFilename}); - m_stderrFile->reset(); - m_stdoutFile->reset(); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully imported database.\n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully imported database.\n")); - - Utils::Test::setNextPassword("a"); - auto db = QSharedPointer(Utils::unlockDatabase(databaseFilename, true, "", "", Utils::DEVNULL)); + auto db = readDatabase(databaseFilename, "a"); QVERIFY(db); auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry 1"); QVERIFY(entry); QCOMPARE(entry->username(), QString("User Name")); // Should refuse to create the database if it already exists. - qint64 pos = m_stdoutFile->pos(); - qint64 errPos = m_stderrFile->pos(); - importCmd.execute({"import", m_xmlFile->fileName(), databaseFilename}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(errPos); + execCmd(importCmd, {"import", m_xmlFile->fileName(), databaseFilename}); // Output should be empty when there is an error. - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray()); QString errorMessage = QString("File " + databaseFilename + " already exists.\n"); - QCOMPARE(m_stderrFile->readAll(), errorMessage.toUtf8()); + QCOMPARE(m_stderr->readAll(), errorMessage.toUtf8()); // Quiet option QScopedPointer testDirQuiet(new QTemporaryDir()); - QString databaseFilenameQuiet = testDirQuiet->path() + "testImport2.kdbx"; + QString databaseFilenameQuiet = testDirQuiet->path() + "/testImport2.kdbx"; - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a", true); - importCmd.execute({"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet}); - m_stdoutFile->seek(pos); + setInput({"a", "a"}); + execCmd(importCmd, {"import", "-q", m_xmlFile->fileName(), databaseFilenameQuiet}); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Repeat password: \n")); - QCOMPARE(m_stdoutFile->readLine(), QByteArray()); + QCOMPARE(m_stderr->readLine(), QByteArray("Enter password to encrypt database (optional): \n")); + QCOMPARE(m_stderr->readLine(), QByteArray("Repeat password: \n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); - Utils::Test::setNextPassword("a"); - auto dbQuiet = QSharedPointer(Utils::unlockDatabase(databaseFilenameQuiet, true, "", "", Utils::DEVNULL)); - QVERIFY(dbQuiet); + db = readDatabase(databaseFilenameQuiet, "a"); + QVERIFY(db); } void TestCli::testKeyFileOption() @@ -1221,35 +1049,26 @@ void TestCli::testKeyFileOption() List listCmd; QString keyFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtected.key")); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-k", keyFilePath, m_keyFileProtectedDbFile->fileName()}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-k", keyFilePath, m_keyFileProtectedDbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("entry1\n" "entry2\n")); // Should raise an error with no key file. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_keyFileProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QVERIFY(m_stderrFile->readAll().contains("Invalid credentials were provided")); + setInput("a"); + execCmd(listCmd, {"ls", m_keyFileProtectedDbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("Invalid credentials were provided")); // Should raise an error if key file path is invalid. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-k", "invalidpath", m_keyFileProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll().split(':').at(0), QByteArray("Failed to load key file invalidpath")); + setInput("a"); + execCmd(listCmd, {"ls", "-k", "invalidpath", m_keyFileProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stdout->readAll(), QByteArray()); + QCOMPARE(m_stderr->readAll().split(':').at(0), QByteArray("Failed to load key file invalidpath")); } void TestCli::testNoPasswordOption() @@ -1257,21 +1076,17 @@ void TestCli::testNoPasswordOption() List listCmd; QString keyFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtectedNoPassword.key")); - listCmd.execute({"ls", "-k", keyFilePath, "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); - m_stdoutFile->reset(); - QCOMPARE(m_stdoutFile->readAll(), + execCmd(listCmd, {"ls", "-k", keyFilePath, "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); + // Expecting no password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("entry1\n" "entry2\n")); // Should raise an error with no key file. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - listCmd.execute({"ls", "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QVERIFY(m_stderrFile->readAll().contains("Invalid credentials were provided")); + execCmd(listCmd, {"ls", "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("Invalid credentials were provided")); } void TestCli::testList() @@ -1280,11 +1095,11 @@ void TestCli::testList() QVERIFY(!listCmd.name.isEmpty()); QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name)); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_dbFile->fileName()}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", m_dbFile->fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" "Windows/\n" @@ -1294,11 +1109,10 @@ void TestCli::testList() "Homebanking/\n")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-q", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-q", m_dbFile->fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" "Windows/\n" @@ -1307,12 +1121,9 @@ void TestCli::testList() "eMail/\n" "Homebanking/\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-R", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-R", m_dbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" " [empty]\n" @@ -1328,12 +1139,9 @@ void TestCli::testList() " Subgroup/\n" " Subgroup Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-R", "-f", m_dbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-R", "-f", m_dbFile->fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "General/\n" "General/[empty]\n" @@ -1349,30 +1157,21 @@ void TestCli::testList() "Homebanking/Subgroup/\n" "Homebanking/Subgroup/Subgroup Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-R", "-f", m_dbFile->fileName(), "/Homebanking"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-R", "-f", m_dbFile->fileName(), "/Homebanking"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Subgroup/\n" "Subgroup/Subgroup Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_dbFile->fileName(), "/General/"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("[empty]\n")); + setInput("a"); + execCmd(listCmd, {"ls", m_dbFile->fileName(), "/General/"}); + QCOMPARE(m_stdout->readAll(), QByteArray("[empty]\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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")); + setInput("a"); + execCmd(listCmd, {"ls", m_dbFile->fileName(), "/DoesNotExist/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Cannot find group /DoesNotExist/.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testLocate() @@ -1381,30 +1180,26 @@ void TestCli::testLocate() QVERIFY(!locateCmd.name.isEmpty()); QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name)); - Utils::Test::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")); + setInput("a"); + execCmd(locateCmd, {"locate", m_dbFile->fileName(), "Sample"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("/Sample Entry\n")); // Quiet option - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - locateCmd.execute({"locate", m_dbFile->fileName(), "-q", "Sample"}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n")); + setInput("a"); + execCmd(locateCmd, {"locate", m_dbFile->fileName(), "-q", "Sample"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("/Sample Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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")); + setInput("a"); + execCmd(locateCmd, {"locate", m_dbFile->fileName(), "Does Not Exist"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("No results for that search term.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // write a modified database - auto db = readTestDatabase(); + auto db = readDatabase(); QVERIFY(db); auto* group = db->rootGroup()->findGroupByPath("/General/"); QVERIFY(group); @@ -1412,25 +1207,19 @@ void TestCli::testLocate() entry->setUuid(QUuid::createUuid()); entry->setTitle("New Entry"); group->addEntry(entry); + TemporaryFile tmpFile; tmpFile.open(); - Kdbx4Writer writer; - writer.writeDatabase(&tmpFile, db.data()); tmpFile.close(); + db->saveAs(tmpFile.fileName()); - pos = m_stdoutFile->pos(); - Utils::Test::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")); + setInput("a"); + execCmd(locateCmd, {"locate", tmpFile.fileName(), "New"}); + QCOMPARE(m_stdout->readAll(), QByteArray("/General/New Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - locateCmd.execute({"locate", tmpFile.fileName(), "Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(locateCmd, {"locate", tmpFile.fileName(), "Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("/Sample Entry\n/General/New Entry\n/Homebanking/Subgroup/Subgroup Entry\n")); } @@ -1440,30 +1229,32 @@ void TestCli::testMerge() QVERIFY(!mergeCmd.name.isEmpty()); QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name)); - Kdbx4Writer writer; - Kdbx4Reader reader; - // load test database and save copies - auto db = readTestDatabase(); + auto db = readDatabase(); QVERIFY(db); TemporaryFile targetFile1; targetFile1.open(); - writer.writeDatabase(&targetFile1, db.data()); targetFile1.close(); + TemporaryFile targetFile2; targetFile2.open(); - writer.writeDatabase(&targetFile2, db.data()); targetFile2.close(); - // save another copy with a different password TemporaryFile targetFile3; targetFile3.open(); + targetFile3.close(); + + db->saveAs(targetFile1.fileName()); + db->saveAs(targetFile2.fileName()); + + // save another copy with a different password auto oldKey = db->key(); auto key = QSharedPointer::create(); key->addKey(QSharedPointer::create("b")); db->setKey(key); - writer.writeDatabase(&targetFile3, db.data()); - targetFile3.close(); + db->saveAs(targetFile3.fileName()); + + // Restore the original password db->setKey(oldKey); // then add a new entry to the in-memory database and save another copy @@ -1474,91 +1265,64 @@ void TestCli::testMerge() auto* group = db->rootGroup()->findGroupByPath("/Internet/"); QVERIFY(group); group->addEntry(entry); + TemporaryFile sourceFile; sourceFile.open(); - writer.writeDatabase(&sourceFile, db.data()); sourceFile.close(); + db->saveAs(sourceFile.fileName()); - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stderrFile->reset(); - QList outLines1 = m_stdoutFile->readAll().split('\n'); - QCOMPARE(outLines1.at(0).split('[').at(0), QByteArray("\tOverwriting Internet ")); - QCOMPARE(outLines1.at(1).split('[').at(0), QByteArray("\tCreating missing Some Website ")); + setInput("a"); + execCmd(mergeCmd, {"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QList outLines1 = m_stdout->readAll().split('\n'); + QVERIFY(outLines1.at(0).contains("Overwriting Internet")); + QVERIFY(outLines1.at(1).contains("Creating missing Some Website")); QCOMPARE(outLines1.at(2), QString("Successfully merged %1 into %2.").arg(sourceFile.fileName(), targetFile1.fileName()).toUtf8()); - QFile readBack(targetFile1.fileName()); - readBack.open(QIODevice::ReadOnly); auto mergedDb = QSharedPointer::create(); - reader.readDatabase(&readBack, oldKey, mergedDb.data()); - readBack.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile1.fileName(), oldKey)); + auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(entry1); QCOMPARE(entry1->title(), QString("Some Website")); QCOMPARE(entry1->password(), QString("secretsecretsecret")); // the dry run option should not modify the target database. - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "--dry-run", "-s", targetFile2.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stderrFile->reset(); - QList outLines2 = m_stdoutFile->readAll().split('\n'); - QCOMPARE(outLines2.at(0).split('[').at(0), QByteArray("\tOverwriting Internet ")); - QCOMPARE(outLines2.at(1).split('[').at(0), QByteArray("\tCreating missing Some Website ")); + setInput("a"); + execCmd(mergeCmd, {"merge", "--dry-run", "-s", targetFile2.fileName(), sourceFile.fileName()}); + QList outLines2 = m_stdout->readAll().split('\n'); + QVERIFY(outLines2.at(0).contains("Overwriting Internet")); + QVERIFY(outLines2.at(1).contains("Creating missing Some Website")); QCOMPARE(outLines2.at(2), QByteArray("Database was not modified by merge operation.")); - QFile readBack2(targetFile2.fileName()); - readBack2.open(QIODevice::ReadOnly); mergedDb = QSharedPointer::create(); - reader.readDatabase(&readBack2, oldKey, mergedDb.data()); - readBack2.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile2.fileName(), oldKey)); entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(!entry1); // the dry run option can be used with the quiet option - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "--dry-run", "-s", "-q", targetFile2.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stderrFile->reset(); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(mergeCmd, {"merge", "--dry-run", "-s", "-q", targetFile2.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); - readBack2.setFileName(targetFile2.fileName()); - readBack2.open(QIODevice::ReadOnly); mergedDb = QSharedPointer::create(); - reader.readDatabase(&readBack2, oldKey, mergedDb.data()); - readBack2.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile2.fileName(), oldKey)); entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(!entry1); // try again with different passwords for both files - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("b"); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", targetFile3.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - m_stdoutFile->readLine(); - QList outLines3 = m_stdoutFile->readAll().split('\n'); + setInput({"b", "a"}); + execCmd(mergeCmd, {"merge", targetFile3.fileName(), sourceFile.fileName()}); + QList outLines3 = m_stdout->readAll().split('\n'); QCOMPARE(outLines3.at(2), QString("Successfully merged %1 into %2.").arg(sourceFile.fileName(), targetFile3.fileName()).toUtf8()); - readBack.setFileName(targetFile3.fileName()); - readBack.open(QIODevice::ReadOnly); mergedDb = QSharedPointer::create(); - reader.readDatabase(&readBack, key, mergedDb.data()); - readBack.close(); - QVERIFY(mergedDb); + QVERIFY(mergedDb->open(targetFile3.fileName(), key)); + entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); QVERIFY(entry1); QCOMPARE(entry1->title(), QString("Some Website")); @@ -1566,27 +1330,21 @@ void TestCli::testMerge() // making sure that the message is different if the database was not // modified by the merge operation. - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-s", sourceFile.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Database was not modified by merge operation.\n")); + setInput("a"); + execCmd(mergeCmd, {"merge", "-s", sourceFile.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stdout->readAll(), QByteArray("Database was not modified by merge operation.\n")); // Quiet option - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-q", "-s", sourceFile.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(mergeCmd, {"merge", "-q", "-s", sourceFile.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Quiet option without the -s option - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", "-q", sourceFile.fileName(), sourceFile.fileName()}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput({"a", "a"}); + execCmd(mergeCmd, {"merge", "-q", sourceFile.fileName(), sourceFile.fileName()}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testMergeWithKeys() @@ -1599,9 +1357,6 @@ void TestCli::testMergeWithKeys() QVERIFY(!mergeCmd.name.isEmpty()); QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name)); - Kdbx4Writer writer; - Kdbx4Reader reader; - QScopedPointer testDir(new QTemporaryDir()); QString sourceDatabaseFilename = testDir->path() + "/testSourceDatabase.kdbx"; @@ -1610,22 +1365,16 @@ void TestCli::testMergeWithKeys() QString targetDatabaseFilename = testDir->path() + "/testTargetDatabase.kdbx"; QString targetKeyfilePath = testDir->path() + "/testTargetKeyfile.txt"; - qint64 pos = m_stdoutFile->pos(); + setInput({"a", "a"}); + execCmd(createCmd, {"db-create", sourceDatabaseFilename, "-p", "-k", sourceKeyfilePath}); - Utils::Test::setNextPassword("a", true); - createCmd.execute({"db-create", sourceDatabaseFilename, "-p", "-k", sourceKeyfilePath}); + setInput({"b", "b"}); + execCmd(createCmd, {"db-create", targetDatabaseFilename, "-p", "-k", targetKeyfilePath}); - Utils::Test::setNextPassword("b", true); - createCmd.execute({"db-create", targetDatabaseFilename, "-p", "-k", targetKeyfilePath}); - - Utils::Test::setNextPassword("a"); - auto sourceDatabase = QSharedPointer( - Utils::unlockDatabase(sourceDatabaseFilename, true, sourceKeyfilePath, "", Utils::STDOUT)); + auto sourceDatabase = readDatabase(sourceDatabaseFilename, "a", sourceKeyfilePath); QVERIFY(sourceDatabase); - Utils::Test::setNextPassword("b"); - auto targetDatabase = QSharedPointer( - Utils::unlockDatabase(targetDatabaseFilename, true, targetKeyfilePath, "", Utils::STDOUT)); + auto targetDatabase = readDatabase(targetDatabaseFilename, "b", targetKeyfilePath); QVERIFY(targetDatabase); auto* rootGroup = new Group(); @@ -1660,34 +1409,22 @@ void TestCli::testMergeWithKeys() targetDatabase->setRootGroup(otherRootGroup); - QFile sourceDatabaseFile(sourceDatabaseFilename); - sourceDatabaseFile.open(QIODevice::WriteOnly); - QVERIFY(writer.writeDatabase(&sourceDatabaseFile, sourceDatabase.data())); - sourceDatabaseFile.flush(); - sourceDatabaseFile.close(); + sourceDatabase->saveAs(sourceDatabaseFilename); + targetDatabase->saveAs(targetDatabaseFilename); - QFile targetDatabaseFile(targetDatabaseFilename); - targetDatabaseFile.open(QIODevice::WriteOnly); - QVERIFY(writer.writeDatabase(&targetDatabaseFile, targetDatabase.data())); - targetDatabaseFile.flush(); - targetDatabaseFile.close(); + setInput({"b", "a"}); + execCmd(mergeCmd, + {"merge", + "-k", + targetKeyfilePath, + "--key-file-from", + sourceKeyfilePath, + targetDatabaseFilename, + sourceDatabaseFilename}); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("b"); - Utils::Test::setNextPassword("a"); - mergeCmd.execute({"merge", - "-k", - targetKeyfilePath, - "--key-file-from", - sourceKeyfilePath, - targetDatabaseFile.fileName(), - sourceDatabaseFile.fileName()}); - - m_stdoutFile->seek(pos); - QList lines = m_stdoutFile->readAll().split('\n'); - QVERIFY(lines.contains(QString("Successfully merged %1 into %2.") - .arg(sourceDatabaseFile.fileName(), targetDatabaseFile.fileName()) - .toUtf8())); + QList lines = m_stdout->readAll().split('\n'); + QVERIFY(lines.contains( + QString("Successfully merged %1 into %2.").arg(sourceDatabaseFilename, targetDatabaseFilename).toUtf8())); } void TestCli::testMove() @@ -1696,53 +1433,37 @@ void TestCli::testMove() QVERIFY(!moveCmd.name.isEmpty()); QVERIFY(moveCmd.getDescriptionLine().contains(moveCmd.name)); - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "invalid_entry_path", "invalid_group_path"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Could not find entry with path invalid_entry_path.\n")); + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "invalid_entry_path", "invalid_group_path"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Could not find entry with path invalid_entry_path.\n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "Sample Entry", "invalid_group_path"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Could not find group with path invalid_group_path.\n")); + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "Sample Entry", "invalid_group_path"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Could not find group with path invalid_group_path.\n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "Sample Entry", "General/"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully moved entry Sample Entry to group General/.\n")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("")); + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "Sample Entry", "General/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray()); + QCOMPARE(m_stdout->readLine(), QByteArray("Successfully moved entry Sample Entry to group General/.\n")); - auto db = readTestDatabase(); + auto db = readDatabase(); auto* entry = db->rootGroup()->findEntryByPath("General/Sample Entry"); QVERIFY(entry); // Test that not modified if the same group is destination. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - moveCmd.execute({"mv", m_dbFile->fileName(), "General/Sample Entry", "General/"}); - m_stdoutFile->seek(pos); - m_stderrFile->seek(posErr); - m_stdoutFile->readLine(); // skip prompt line - QCOMPARE(m_stdoutFile->readLine(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), QByteArray("Entry is already in group General/.\n")); + setInput("a"); + execCmd(moveCmd, {"mv", m_dbFile->fileName(), "General/Sample Entry", "General/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Entry is already in group General/.\n")); + QCOMPARE(m_stdout->readLine(), QByteArray()); // sanity check - db = readTestDatabase(); + db = readDatabase(); entry = db->rootGroup()->findEntryByPath("General/Sample Entry"); QVERIFY(entry); } @@ -1753,80 +1474,52 @@ void TestCli::testRemove() 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(); + auto db = readDatabase(); QVERIFY(db); TemporaryFile fileCopy; fileCopy.open(); - db->metadata()->setRecycleBinEnabled(false); - writer.writeDatabase(&fileCopy, db.data()); fileCopy.close(); - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); + db->metadata()->setRecycleBinEnabled(false); + db->saveAs(fileCopy.fileName()); // delete entry and verify - Utils::Test::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")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeCmd, {"rm", m_dbFile->fileName(), "/Sample Entry"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->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); - auto readBackDb = QSharedPointer::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); + auto readBackDb = readDatabase(); QVERIFY(readBackDb); QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); QVERIFY(readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); - pos = m_stdoutFile->pos(); - pos = m_stdoutFile->pos(); - // try again, this time without recycle bin - Utils::Test::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")); + setInput("a"); + execCmd(removeCmd, {"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully deleted entry Sample Entry.\n")); - readBack.setFileName(fileCopy.fileName()); - readBack.open(QIODevice::ReadOnly); - readBackDb = QSharedPointer::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); + readBackDb = readDatabase(fileCopy.fileName(), "a"); QVERIFY(readBackDb); QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); QVERIFY(!readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); // finally, try deleting a non-existent entry - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n")); + setInput("a"); + execCmd(removeCmd, {"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Entry /Sample Entry not found.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // try deleting a directory, should fail - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", fileCopy.fileName(), "/General"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /General not found.\n")); + setInput("a"); + execCmd(removeCmd, {"rm", fileCopy.fileName(), "/General"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Entry /General not found.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testRemoveGroup() @@ -1835,60 +1528,41 @@ void TestCli::testRemoveGroup() QVERIFY(!removeGroupCmd.name.isEmpty()); QVERIFY(removeGroupCmd.getDescriptionLine().contains(removeGroupCmd.name)); - Kdbx3Reader reader; - Kdbx3Writer writer; - // try deleting a directory, should recycle it first. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "/General"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully recycled group /General.\n")); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "/General"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully recycled group /General.\n")); - auto db = readTestDatabase(); + auto db = readDatabase(); auto* group = db->rootGroup()->findGroupByPath("General"); QVERIFY(!group); // try deleting a directory again, should delete it permanently. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "Recycle Bin/General"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully deleted group Recycle Bin/General.\n")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "Recycle Bin/General"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully deleted group Recycle Bin/General.\n")); - db = readTestDatabase(); + db = readDatabase(); group = db->rootGroup()->findGroupByPath("Recycle Bin/General"); QVERIFY(!group); // try deleting an invalid group, should fail. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "invalid"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Group invalid not found.\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "invalid"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Group invalid not found.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should fail to remove the root group. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - removeGroupCmd.execute({"rmdir", m_dbFile->fileName(), "/"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Cannot remove root group from database.\n")); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeGroupCmd, {"rmdir", m_dbFile->fileName(), "/"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("Cannot remove root group from database.\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } void TestCli::testRemoveQuiet() @@ -1897,44 +1571,27 @@ void TestCli::testRemoveQuiet() QVERIFY(!removeCmd.name.isEmpty()); QVERIFY(removeCmd.getDescriptionLine().contains(removeCmd.name)); - Kdbx3Reader reader; - Kdbx3Writer writer; - - qint64 pos = m_stdoutFile->pos(); - // delete entry and verify - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", "-q", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeCmd, {"rm", "-q", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); - auto key = QSharedPointer::create(); - key->addKey(QSharedPointer::create("a")); - QFile readBack(m_dbFile->fileName()); - readBack.open(QIODevice::ReadOnly); - auto readBackDb = QSharedPointer::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); - QVERIFY(readBackDb); - QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); - QVERIFY(readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); + auto db = readDatabase(); + QVERIFY(db); - pos = m_stdoutFile->pos(); + QVERIFY(!db->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(db->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); // remove the entry completely - Utils::Test::setNextPassword("a"); - removeCmd.execute({"rm", "-q", m_dbFile->fileName(), QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin"))}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + setInput("a"); + execCmd(removeCmd, {"rm", "-q", m_dbFile->fileName(), QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin"))}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray()); - readBack.setFileName(m_dbFile->fileName()); - readBack.open(QIODevice::ReadOnly); - readBackDb = QSharedPointer::create(); - reader.readDatabase(&readBack, key, readBackDb.data()); - readBack.close(); - QVERIFY(readBackDb); - QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); - QVERIFY(!readBackDb->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); + db = readDatabase(); + QVERIFY(!db->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(!db->rootGroup()->findEntryByPath(QString("/%1/Sample Entry").arg(Group::tr("Recycle Bin")))); } void TestCli::testShow() @@ -1943,119 +1600,82 @@ void TestCli::testShow() QVERIFY(!showCmd.name.isEmpty()); QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name)); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(showCmd, {"show", m_dbFile->fileName(), "/Sample Entry"}); + m_stderr->readLine(); // Skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" "Password: PROTECTED\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); - qint64 pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-s", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(showCmd, {"show", "-s", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" "Password: Password\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "-q", "/Sample Entry"}); - m_stdoutFile->seek(pos); - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(showCmd, {"show", m_dbFile->fileName(), "-q", "/Sample Entry"}); + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("Title: Sample Entry\n" "UserName: User Name\n" "Password: PROTECTED\n" "URL: http://www.somesite.com/\n" "Notes: Notes\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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")); + setInput("a"); + execCmd(showCmd, {"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-a", "Password", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), QByteArray("Password\n")); + setInput("a"); + execCmd(showCmd, {"show", "-a", "Password", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Password\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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(), + setInput("a"); + execCmd(showCmd, {"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "http://www.somesite.com/\n")); // Test case insensitivity - pos = m_stdoutFile->pos(); - Utils::Test::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(), + setInput("a"); + execCmd(showCmd, {"show", "-a", "TITLE", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray("Sample Entry\n" "http://www.somesite.com/\n")); - pos = m_stdoutFile->pos(); - Utils::Test::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")); + setInput("a"); + execCmd(showCmd, {"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("ERROR: unknown attribute DoesNotExist.\n")); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-t", m_dbFile->fileName(), "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QVERIFY(isTOTP(m_stdoutFile->readAll())); + setInput("a"); + execCmd(showCmd, {"show", "-t", m_dbFile->fileName(), "/Sample Entry"}); + QVERIFY(isTotp(m_stdout->readAll())); - pos = m_stdoutFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "--totp", "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readLine(), QByteArray("Sample Entry\n")); - QVERIFY(isTOTP(m_stdoutFile->readAll())); + setInput("a"); + execCmd(showCmd, {"show", "-a", "Title", m_dbFile->fileName(), "--totp", "/Sample Entry"}); + QCOMPARE(m_stdout->readLine(), QByteArray("Sample Entry\n")); + QVERIFY(isTotp(m_stdout->readAll())); - pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry with path /Sample Entry has no TOTP set up.\n")); + setInput("a"); + execCmd(showCmd, {"show", m_dbFile2->fileName(), "--totp", "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains("Entry with path /Sample Entry has no TOTP set up.\n")); // Show with ambiguous attributes - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - showCmd.execute({"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE( - m_stderrFile->readAll(), - QByteArray("ERROR: attribute Testattribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); + setInput("a"); + execCmd(showCmd, {"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"}); + QCOMPARE(m_stdout->readAll(), QByteArray()); + QVERIFY(m_stderr->readAll().contains( + "ERROR: attribute Testattribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); } void TestCli::testInvalidDbFiles() @@ -2064,16 +1684,13 @@ void TestCli::testInvalidDbFiles() QString nonExistentDbPath("/foo/bar/baz"); QString directoryName("/"); - qint64 pos = m_stderrFile->pos(); - showCmd.execute({"show", nonExistentDbPath, "-q", "/Sample Entry"}); - m_stderrFile->seek(pos); - QCOMPARE(QString(m_stderrFile->readAll()), + execCmd(showCmd, {"show", nonExistentDbPath, "/Sample Entry"}); + QCOMPARE(QString(m_stderr->readAll()), QObject::tr("Failed to open database file %1: not found").arg(nonExistentDbPath) + "\n"); + QCOMPARE(m_stdout->readAll(), QByteArray()); - pos = m_stderrFile->pos(); - showCmd.execute({"show", directoryName, "-q", "whatever"}); - m_stderrFile->seek(pos); - QCOMPARE(QString(m_stderrFile->readAll()), + execCmd(showCmd, {"show", directoryName, "whatever"}); + QCOMPARE(QString(m_stderr->readAll()), QObject::tr("Failed to open database file %1: not a plain file").arg(directoryName) + "\n"); // Create a write-only file and try to open it. @@ -2085,10 +1702,8 @@ void TestCli::testInvalidDbFiles() QVERIFY(tempFile.open()); QString path = QFileInfo(tempFile).absoluteFilePath(); QVERIFY(tempFile.setPermissions(QFileDevice::WriteOwner)); - pos = m_stderrFile->pos(); - showCmd.execute({"show", path, "some entry"}); - m_stderrFile->seek(pos); - QCOMPARE(QString(m_stderrFile->readAll()), + execCmd(showCmd, {"show", path, "some entry"}); + QCOMPARE(QString(m_stderr->readAll()), QObject::tr("Failed to open database file %1: not readable").arg(path) + "\n"); #endif // Q_OS_WIN } @@ -2120,66 +1735,39 @@ void TestCli::testYubiKeyOption() List listCmd; Add addCmd; - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-y", "2", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->reset(); - m_stderrFile->reset(); - m_stdoutFile->readLine(); // skip password prompt - QCOMPARE(m_stdoutFile->readAll(), + setInput("a"); + execCmd(listCmd, {"ls", "-y", "2", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray()); + QCOMPARE(m_stdout->readAll(), QByteArray("entry1\n" "entry2\n")); // Should raise an error with no yubikey slot. - qint64 pos = m_stdoutFile->pos(); - qint64 posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readLine(), + setInput("a"); + execCmd(listCmd, {"ls", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readLine(), QByteArray("Error while reading the database: Invalid credentials were provided, please try again.\n")); - QCOMPARE(m_stderrFile->readLine(), + QCOMPARE(m_stderr->readLine(), QByteArray("If this reoccurs, then your database file may be corrupt. (HMAC mismatch)\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should raise an error if yubikey slot is not a string - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-y", "invalidslot", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot invalidslot\n")); + setInput("a"); + execCmd(listCmd, {"ls", "-y", "invalidslot", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot invalidslot\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); // Should raise an error if yubikey slot is invalid. - pos = m_stdoutFile->pos(); - posErr = m_stderrFile->pos(); - Utils::Test::setNextPassword("a"); - listCmd.execute({"ls", "-y", "3", m_yubiKeyProtectedDbFile->fileName()}); - m_stdoutFile->seek(pos); - m_stdoutFile->readLine(); // skip password prompt - m_stderrFile->seek(posErr); - QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); - QCOMPARE(m_stderrFile->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot 3\n")); + setInput("a"); + execCmd(listCmd, {"ls", "-y", "3", m_yubiKeyProtectedDbFile->fileName()}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll().split(':').at(0), QByteArray("Invalid YubiKey slot 3\n")); + QCOMPARE(m_stdout->readAll(), QByteArray()); } -namespace -{ - - void expectParseResult(const QString& input, const QStringList& expectedOutput) - { - QStringList result = Utils::splitCommandString(input); - QCOMPARE(result.size(), expectedOutput.size()); - for (int i = 0; i < expectedOutput.size(); ++i) { - QCOMPARE(result[i], expectedOutput[i]); - } - } - -} // namespace - void TestCli::testCommandParsing_data() { QTest::addColumn("input"); @@ -2201,23 +1789,25 @@ void TestCli::testCommandParsing() QFETCH(QString, input); QFETCH(QStringList, expectedOutput); - expectParseResult(input, expectedOutput); + QStringList result = Utils::splitCommandString(input); + QCOMPARE(result.size(), expectedOutput.size()); + for (int i = 0; i < expectedOutput.size(); ++i) { + QCOMPARE(result[i], expectedOutput[i]); + } } void TestCli::testOpen() { - Open o; + Open openCmd; - Utils::Test::setNextPassword("a"); - o.execute({"open", m_dbFile->fileName()}); - m_stdoutFile->reset(); - QVERIFY(o.currentDatabase); + setInput("a"); + execCmd(openCmd, {"open", m_dbFile->fileName()}); + QVERIFY(openCmd.currentDatabase); - List l; + List listCmd; // Set a current database, simulating interactive mode. - l.currentDatabase = o.currentDatabase; - l.execute({"ls"}); - m_stdoutFile->reset(); + listCmd.currentDatabase = openCmd.currentDatabase; + execCmd(listCmd, {"ls"}); QByteArray expectedOutput("Sample Entry\n" "General/\n" "Windows/\n" @@ -2225,28 +1815,20 @@ void TestCli::testOpen() "Internet/\n" "eMail/\n" "Homebanking/\n"); - QByteArray actualOutput = m_stdoutFile->readAll(); + QByteArray actualOutput = m_stdout->readAll(); actualOutput.truncate(expectedOutput.length()); QCOMPARE(actualOutput, expectedOutput); } void TestCli::testHelp() { - Help h; + Help helpCmd; Commands::setupCommands(false); - { - h.execute({"help"}); - m_stderrFile->reset(); - QString output(m_stderrFile->readAll()); - QVERIFY(output.contains(QObject::tr("Available commands"))); - } + execCmd(helpCmd, {"help"}); + QVERIFY(m_stdout->readAll().contains("Available commands")); - { - List l; - h.execute({"help", "ls"}); - m_stderrFile->reset(); - QString output(m_stderrFile->readAll()); - QVERIFY(output.contains(l.description)); - } + List listCmd; + execCmd(helpCmd, {"help", "ls"}); + QVERIFY(m_stdout->readAll().contains(listCmd.description.toLatin1())); } diff --git a/tests/TestCli.h b/tests/TestCli.h index 44420d580..a8e6eabbb 100644 --- a/tests/TestCli.h +++ b/tests/TestCli.h @@ -18,30 +18,33 @@ #ifndef KEEPASSXC_TESTCLI_H #define KEEPASSXC_TESTCLI_H -#include "core/Database.h" -#include "util/TemporaryFile.h" - -#include -#include +#include #include #include -#include +#include #include -#include +#include "util/TemporaryFile.h" + +class Command; +class Database; class TestCli : public QObject { Q_OBJECT private: - QSharedPointer readTestDatabase() const; + QSharedPointer + readDatabase(const QString& filename = {}, const QString& pw = {}, const QString& keyfile = {}); + int execCmd(Command& cmd, const QStringList& args) const; + bool isTotp(const QString& value); + void setInput(const QString& input); + void setInput(const QStringList& input); private slots: void initTestCase(); void init(); void cleanup(); - void cleanupTestCase(); void testBatchCommands(); void testAdd(); @@ -78,21 +81,16 @@ private slots: void testYubiKeyOption(); private: - QByteArray m_dbData; - QByteArray m_dbData2; - QByteArray m_xmlData; - QByteArray m_yubiKeyProtectedDbData; - QByteArray m_keyFileProtectedDbData; - QByteArray m_keyFileProtectedNoPasswordDbData; QScopedPointer m_dbFile; QScopedPointer m_dbFile2; QScopedPointer m_xmlFile; QScopedPointer m_keyFileProtectedDbFile; QScopedPointer m_keyFileProtectedNoPasswordDbFile; QScopedPointer m_yubiKeyProtectedDbFile; - QScopedPointer m_stdoutFile; - QScopedPointer m_stderrFile; - QScopedPointer m_stdinFile; + + QScopedPointer m_stdout; + QScopedPointer m_stderr; + QScopedPointer m_stdin; }; #endif // KEEPASSXC_TESTCLI_H