CLI: Use stderr for password prompt

Fixes #3398.

Convert to QTextStream for all CLI IO and greatly improve CLI tests

* Completely overhaul CLI tests to be much more streamlined and easy to read. Removed unnecessary code blocks by using existing functions.

Co-authored-by: Emma Brooks <me@pluvano.com>
This commit is contained in:
Jonathan White 2020-05-11 07:31:29 -04:00
parent 612f8d2e5b
commit 485852c9db
30 changed files with 938 additions and 1407 deletions

View File

@ -69,16 +69,15 @@ Add::Add()
int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); auto& out = Utils::STDOUT;
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& err = Utils::STDERR;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
auto& entryPath = args.at(1); auto& entryPath = args.at(1);
// Cannot use those 2 options at the same time! // Cannot use those 2 options at the same time!
if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) { 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; return EXIT_FAILURE;
} }
@ -94,7 +93,7 @@ int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<Q
Entry* entry = database->rootGroup()->addEntryWithPath(entryPath); Entry* entry = database->rootGroup()->addEntryWithPath(entryPath);
if (!entry) { 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; return EXIT_FAILURE;
} }
@ -108,9 +107,9 @@ int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<Q
if (parser->isSet(Add::PasswordPromptOption)) { if (parser->isSet(Add::PasswordPromptOption)) {
if (!parser->isSet(Command::QuietOption)) { 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); entry->setPassword(password);
} else if (parser->isSet(Add::GenerateOption)) { } else if (parser->isSet(Add::GenerateOption)) {
QString password = passwordGenerator->generatePassword(); QString password = passwordGenerator->generatePassword();
@ -119,12 +118,12 @@ int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<Q
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
if (!parser->isSet(Command::QuietOption)) { 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; return EXIT_SUCCESS;
} }

View File

@ -39,8 +39,8 @@ AddGroup::~AddGroup()
int AddGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int AddGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& groupPath = args.at(1); const QString& groupPath = args.at(1);
@ -51,13 +51,13 @@ int AddGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPoin
Group* group = database->rootGroup()->findGroupByPath(groupPath); Group* group = database->rootGroup()->findGroupByPath(groupPath);
if (group) { 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; return EXIT_FAILURE;
} }
Group* parentGroup = database->rootGroup()->findGroupByPath(parentGroupPath); Group* parentGroup = database->rootGroup()->findGroupByPath(parentGroupPath);
if (!parentGroup) { 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; return EXIT_FAILURE;
} }
@ -68,12 +68,12 @@ int AddGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPoin
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
if (!parser->isSet(Command::QuietOption)) { 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; return EXIT_SUCCESS;
} }

View File

@ -43,29 +43,27 @@ Analyze::Analyze()
int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); auto& out = Utils::STDOUT;
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& err = Utils::STDERR;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption); QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption);
QFile hibpFile(hibpDatabase); QFile hibpFile(hibpDatabase);
if (!hibpFile.open(QFile::ReadOnly)) { if (!hibpFile.open(QFile::ReadOnly)) {
errorTextStream << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl;
<< endl;
return EXIT_FAILURE; 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<QPair<const Entry*, int>> findings; QList<QPair<const Entry*, int>> findings;
QString error; QString error;
if (!HibpOffline::report(database, hibpFile, findings, &error)) { if (!HibpOffline::report(database, hibpFile, findings, &error)) {
errorTextStream << error << endl; err << error << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
for (auto& finding : findings) { for (auto& finding : findings) {
printHibpFinding(finding.first, finding.second, outputTextStream); printHibpFinding(finding.first, finding.second, out);
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -53,32 +53,32 @@ Clip::Clip()
int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1); const QString& entryPath = args.at(1);
QString timeout; QString timeout;
if (args.size() == 3) { if (args.size() == 3) {
timeout = args.at(2); timeout = args.at(2);
} }
TextStream errorTextStream(Utils::STDERR);
int timeoutSeconds = 0; int timeoutSeconds = 0;
if (!timeout.isEmpty() && timeout.toInt() <= 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; return EXIT_FAILURE;
} else if (!timeout.isEmpty()) { } else if (!timeout.isEmpty()) {
timeoutSeconds = timeout.toInt(); timeoutSeconds = timeout.toInt();
} }
TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
QIODevice::WriteOnly);
Entry* entry = database->rootGroup()->findEntryByPath(entryPath); Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) { 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; return EXIT_FAILURE;
} }
if (parser->isSet(AttributeOption) && parser->isSet(TotpOption)) { 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; return EXIT_FAILURE;
} }
@ -87,7 +87,7 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
bool found = false; bool found = false;
if (parser->isSet(TotpOption) || selectedAttribute == "totp") { if (parser->isSet(TotpOption) || selectedAttribute == "totp") {
if (!entry->hasTotp()) { 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; return EXIT_FAILURE;
} }
@ -96,9 +96,9 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
} else { } else {
QStringList attrs = Utils::findAttributes(*entry->attributes(), selectedAttribute); QStringList attrs = Utils::findAttributes(*entry->attributes(), selectedAttribute);
if (attrs.size() > 1) { if (attrs.size() > 1) {
errorTextStream << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.") err << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.")
.arg(selectedAttribute, QLocale().createSeparatedList(attrs)) .arg(selectedAttribute, QLocale().createSeparatedList(attrs))
<< endl; << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} else if (attrs.size() == 1) { } else if (attrs.size() == 1) {
found = true; found = true;
@ -108,7 +108,7 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
} }
if (!found) { 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; return EXIT_FAILURE;
} }
@ -117,7 +117,7 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
return exitCode; 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) { if (!timeoutSeconds) {
return exitCode; return exitCode;
@ -125,15 +125,15 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
QString lastLine = ""; QString lastLine = "";
while (timeoutSeconds > 0) { 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); 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)); std::this_thread::sleep_for(std::chrono::milliseconds(1000));
--timeoutSeconds; --timeoutSeconds;
} }
Utils::clipText(""); Utils::clipText("");
outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; out << '\r' << QString(lastLine.size(), ' ') << '\r';
outputTextStream << QObject::tr("Clipboard cleared!") << endl; out << QObject::tr("Clipboard cleared!") << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -126,24 +126,24 @@ QString Command::getHelpText()
QSharedPointer<QCommandLineParser> Command::getCommandLineParser(const QStringList& arguments) QSharedPointer<QCommandLineParser> Command::getCommandLineParser(const QStringList& arguments)
{ {
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
QSharedPointer<QCommandLineParser> parser = buildParser(this); QSharedPointer<QCommandLineParser> parser = buildParser(this);
if (!parser->parse(arguments)) { if (!parser->parse(arguments)) {
errorTextStream << parser->errorText() << "\n\n"; err << parser->errorText() << "\n\n";
errorTextStream << getHelpText(); err << getHelpText();
return {}; return {};
} }
if (parser->positionalArguments().size() < positionalArguments.size()) { if (parser->positionalArguments().size() < positionalArguments.size()) {
errorTextStream << getHelpText(); err << getHelpText();
return {}; return {};
} }
if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) { if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) {
errorTextStream << getHelpText(); err << getHelpText();
return {}; return {};
} }
if (parser->isSet(HelpOption)) { if (parser->isSet(HelpOption)) {
errorTextStream << getHelpText(); err << getHelpText();
return {}; return {};
} }
return parser; return parser;

View File

@ -77,10 +77,8 @@ int Create::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
bool quiet = parser->isSet(Command::QuietOption); auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
auto& err = Utils::STDERR;
QTextStream out(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly);
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
@ -110,7 +108,7 @@ int Create::execute(const QStringList& arguments)
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
if (parser->isSet(Create::SetPasswordOption)) { if (parser->isSet(Create::SetPasswordOption)) {
auto passwordKey = Utils::getPasswordFromStdin(); auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) { if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl; err << QObject::tr("Failed to set database password.") << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
@ -151,7 +149,7 @@ int Create::execute(const QStringList& arguments)
bool ok = db->changeKdf(kdf); bool ok = db->changeKdf(kdf);
if (!ok) { 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; return EXIT_FAILURE;
} }
} }
@ -180,8 +178,7 @@ int Create::execute(const QStringList& arguments)
*/ */
bool Create::loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey) bool Create::loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey)
{ {
QTextStream err(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
QString error; QString error;
fileKey = QSharedPointer<FileKey>(new FileKey()); fileKey = QSharedPointer<FileKey>(new FileKey());

View File

@ -57,8 +57,7 @@ int DatabaseCommand::execute(const QStringList& arguments)
#else #else
"", "",
#endif #endif
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, parser->isSet(Command::QuietOption));
Utils::STDERR);
if (!db) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -51,8 +51,8 @@ int Diceware::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
PassphraseGenerator dicewareGenerator; PassphraseGenerator dicewareGenerator;
@ -60,7 +60,7 @@ int Diceware::execute(const QStringList& arguments)
if (wordCount.isEmpty()) { if (wordCount.isEmpty()) {
dicewareGenerator.setWordCount(PassphraseGenerator::DefaultWordCount); dicewareGenerator.setWordCount(PassphraseGenerator::DefaultWordCount);
} else if (wordCount.toInt() <= 0) { } 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; return EXIT_FAILURE;
} else { } else {
dicewareGenerator.setWordCount(wordCount.toInt()); dicewareGenerator.setWordCount(wordCount.toInt());
@ -74,12 +74,12 @@ int Diceware::execute(const QStringList& arguments)
if (!dicewareGenerator.isValid()) { if (!dicewareGenerator.isValid()) {
// We already validated the word count input so if the generator is invalid, it // We already validated the word count input so if the generator is invalid, it
// must be because the word list is too small. // 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; return EXIT_FAILURE;
} }
QString password = dicewareGenerator.generatePassphrase(); QString password = dicewareGenerator.generatePassphrase();
outputTextStream << password << endl; out << password << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -60,16 +60,15 @@ Edit::Edit()
int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
QIODevice::WriteOnly); auto& err = Utils::STDERR;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1); const QString& entryPath = args.at(1);
// Cannot use those 2 options at the same time! // Cannot use those 2 options at the same time!
if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) { 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; return EXIT_FAILURE;
} }
@ -86,7 +85,7 @@ int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
Entry* entry = database->rootGroup()->findEntryByPath(entryPath); Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) { 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; return EXIT_FAILURE;
} }
@ -95,7 +94,7 @@ int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
QString title = parser->value(Edit::TitleOption); QString title = parser->value(Edit::TitleOption);
bool prompt = parser->isSet(Add::PasswordPromptOption); bool prompt = parser->isSet(Add::PasswordPromptOption);
if (username.isEmpty() && url.isEmpty() && title.isEmpty() && !prompt && !generate) { 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; return EXIT_FAILURE;
} }
@ -114,8 +113,8 @@ int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
} }
if (prompt) { if (prompt) {
outputTextStream << QObject::tr("Enter new password for entry: ") << flush; out << QObject::tr("Enter new password for 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); entry->setPassword(password);
} else if (generate) { } else if (generate) {
QString password = passwordGenerator->generatePassword(); QString password = passwordGenerator->generatePassword();
@ -126,10 +125,10 @@ int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; 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; return EXIT_SUCCESS;
} }

View File

@ -48,7 +48,7 @@ Estimate::Estimate()
static void estimate(const char* pwd, bool advanced) static void estimate(const char* pwd, bool advanced)
{ {
TextStream out(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
int len = static_cast<int>(strlen(pwd)); int len = static_cast<int>(strlen(pwd));
if (!advanced) { if (!advanced) {
@ -163,14 +163,14 @@ int Estimate::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); auto& in = Utils::STDIN;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
QString password; QString password;
if (args.size() == 1) { if (args.size() == 1) {
password = args.at(0); password = args.at(0);
} else { } else {
password = inputTextStream.readLine(); password = in.readLine();
} }
estimate(password.toLatin1(), parser->isSet(Estimate::AdvancedOption)); estimate(password.toLatin1(), parser->isSet(Estimate::AdvancedOption));

View File

@ -40,23 +40,23 @@ Export::Export()
int Export::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Export::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); TextStream out(Utils::STDOUT.device());
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
QString format = parser->value(Export::FormatOption); QString format = parser->value(Export::FormatOption);
if (format.isEmpty() || format.startsWith(QStringLiteral("xml"), Qt::CaseInsensitive)) { if (format.isEmpty() || format.startsWith(QStringLiteral("xml"), Qt::CaseInsensitive)) {
QByteArray xmlData; QByteArray xmlData;
QString errorMessage; QString errorMessage;
if (!database->extract(xmlData, &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; return EXIT_FAILURE;
} }
outputTextStream.write(xmlData.constData()); out.write(xmlData.constData());
} else if (format.startsWith(QStringLiteral("csv"), Qt::CaseInsensitive)) { } else if (format.startsWith(QStringLiteral("csv"), Qt::CaseInsensitive)) {
CsvExporter csvExporter; CsvExporter csvExporter;
outputTextStream << csvExporter.exportDatabase(database); out << csvExporter.exportDatabase(database);
} else { } else {
errorTextStream << QObject::tr("Unsupported format %1").arg(format) << endl; err << QObject::tr("Unsupported format %1").arg(format) << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -80,13 +80,13 @@ Generate::Generate()
*/ */
QSharedPointer<PasswordGenerator> Generate::createGenerator(QSharedPointer<QCommandLineParser> parser) QSharedPointer<PasswordGenerator> Generate::createGenerator(QSharedPointer<QCommandLineParser> parser)
{ {
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
QSharedPointer<PasswordGenerator> passwordGenerator = QSharedPointer<PasswordGenerator>(new PasswordGenerator()); QSharedPointer<PasswordGenerator> passwordGenerator = QSharedPointer<PasswordGenerator>(new PasswordGenerator());
QString passwordLength = parser->value(Generate::PasswordLengthOption); QString passwordLength = parser->value(Generate::PasswordLengthOption);
if (passwordLength.isEmpty()) { if (passwordLength.isEmpty()) {
passwordGenerator->setLength(PasswordGenerator::DefaultLength); passwordGenerator->setLength(PasswordGenerator::DefaultLength);
} else if (passwordLength.toInt() <= 0) { } 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<PasswordGenerator>(nullptr); return QSharedPointer<PasswordGenerator>(nullptr);
} else { } else {
passwordGenerator->setLength(passwordLength.toInt()); passwordGenerator->setLength(passwordLength.toInt());
@ -126,7 +126,7 @@ QSharedPointer<PasswordGenerator> Generate::createGenerator(QSharedPointer<QComm
passwordGenerator->setExcludedChars(parser->value(Generate::ExcludeCharsOption)); passwordGenerator->setExcludedChars(parser->value(Generate::ExcludeCharsOption));
if (!passwordGenerator->isValid()) { 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<PasswordGenerator>(nullptr); return QSharedPointer<PasswordGenerator>(nullptr);
} }
@ -145,9 +145,9 @@ int Generate::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
QString password = passwordGenerator->generatePassword(); QString password = passwordGenerator->generatePassword();
outputTextStream << password << endl; out << password << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -29,7 +29,7 @@ Help::Help()
int Help::execute(const QStringList& arguments) int Help::execute(const QStringList& arguments)
{ {
TextStream out(Utils::STDERR, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
QSharedPointer<Command> command; QSharedPointer<Command> command;
if (arguments.size() > 1 && (command = Commands::getCommand(arguments.at(1)))) { if (arguments.size() > 1 && (command = Commands::getCommand(arguments.at(1)))) {
out << command->getHelpText(); out << command->getHelpText();
@ -39,5 +39,6 @@ int Help::execute(const QStringList& arguments)
out << cmd->getDescriptionLine(); out << cmd->getDescriptionLine();
} }
} }
out.flush();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -55,30 +55,29 @@ int Import::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
QIODevice::WriteOnly); auto& err = Utils::STDERR;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& xmlExportPath = args.at(0); const QString& xmlExportPath = args.at(0);
const QString& dbPath = args.at(1); const QString& dbPath = args.at(1);
if (QFileInfo::exists(dbPath)) { 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; return EXIT_FAILURE;
} }
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
auto passwordKey = Utils::getPasswordFromStdin(); auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) { if (passwordKey.isNull()) {
errorTextStream << QObject::tr("Failed to set database password.") << endl; err << QObject::tr("Failed to set database password.") << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
key->addKey(passwordKey); key->addKey(passwordKey);
if (key->isEmpty()) { 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; return EXIT_FAILURE;
} }
@ -88,15 +87,15 @@ int Import::execute(const QStringList& arguments)
db.setKey(key); db.setKey(key);
if (!db.import(xmlExportPath, &errorMessage)) { 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; return EXIT_FAILURE;
} }
if (!db.saveAs(dbPath, &errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
outputTextStream << QObject::tr("Successfully imported database.") << endl; out << QObject::tr("Successfully imported database.") << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -33,7 +33,7 @@ Info::Info()
int Info::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser>) int Info::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser>)
{ {
TextStream out(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
out << QObject::tr("UUID: ") << database->uuid().toString() << endl; out << QObject::tr("UUID: ") << database->uuid().toString() << endl;
out << QObject::tr("Name: ") << database->metadata()->name() << endl; out << QObject::tr("Name: ") << database->metadata()->name() << endl;

View File

@ -47,8 +47,8 @@ List::List()
int List::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int List::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
bool recursive = parser->isSet(List::RecursiveOption); bool recursive = parser->isSet(List::RecursiveOption);
@ -56,17 +56,17 @@ int List::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
// No group provided, defaulting to root group. // No group provided, defaulting to root group.
if (args.size() == 1) { if (args.size() == 1) {
outputTextStream << database->rootGroup()->print(recursive, flatten) << flush; out << database->rootGroup()->print(recursive, flatten) << flush;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
const QString& groupPath = args.at(1); const QString& groupPath = args.at(1);
Group* group = database->rootGroup()->findGroupByPath(groupPath); Group* group = database->rootGroup()->findGroupByPath(groupPath);
if (!group) { 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; return EXIT_FAILURE;
} }
outputTextStream << group->print(recursive, flatten) << flush; out << group->print(recursive, flatten) << flush;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -38,20 +38,20 @@ Locate::Locate()
int Locate::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Locate::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
auto& out = Utils::STDOUT;
auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& searchTerm = args.at(1); const QString& searchTerm = args.at(1);
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QStringList results = database->rootGroup()->locate(searchTerm); QStringList results = database->rootGroup()->locate(searchTerm);
if (results.isEmpty()) { if (results.isEmpty()) {
errorTextStream << "No results for that search term." << endl; err << "No results for that search term." << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
for (const QString& result : asConst(results)) { for (const QString& result : asConst(results)) {
outputTextStream << result << endl; out << result << endl;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -61,9 +61,8 @@ Merge::Merge()
int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
QIODevice::WriteOnly); auto& err = Utils::STDERR;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
@ -76,8 +75,7 @@ int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer
!parser->isSet(Merge::NoPasswordFromOption), !parser->isSet(Merge::NoPasswordFromOption),
parser->value(Merge::KeyFileFromOption), parser->value(Merge::KeyFileFromOption),
parser->value(Merge::YubiKeyFromOption), parser->value(Merge::YubiKeyFromOption),
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT, parser->isSet(Command::QuietOption));
Utils::STDERR);
if (!db2) { if (!db2) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -85,7 +83,7 @@ int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer
db2 = QSharedPointer<Database>::create(); db2 = QSharedPointer<Database>::create();
QString errorMessage; QString errorMessage;
if (!db2->open(fromDatabasePath, database->key(), &errorMessage, false)) { 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; return EXIT_FAILURE;
} }
} }
@ -94,19 +92,18 @@ int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer
QStringList changeList = merger.merge(); QStringList changeList = merger.merge();
for (auto& mergeChange : changeList) { for (auto& mergeChange : changeList) {
outputTextStream << "\t" << mergeChange << endl; out << "\t" << mergeChange << endl;
} }
if (!changeList.isEmpty() && !parser->isSet(Merge::DryRunOption)) { if (!changeList.isEmpty() && !parser->isSet(Merge::DryRunOption)) {
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
outputTextStream << QObject::tr("Successfully merged %1 into %2.").arg(fromDatabasePath, toDatabasePath) out << QObject::tr("Successfully merged %1 into %2.").arg(fromDatabasePath, toDatabasePath) << endl;
<< endl;
} else { } 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; return EXIT_SUCCESS;

View File

@ -40,8 +40,8 @@ Move::~Move()
int Move::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Move::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1); const QString& entryPath = args.at(1);
@ -49,18 +49,18 @@ int Move::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
Entry* entry = database->rootGroup()->findEntryByPath(entryPath); Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) { 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; return EXIT_FAILURE;
} }
Group* destinationGroup = database->rootGroup()->findGroupByPath(destinationPath); Group* destinationGroup = database->rootGroup()->findGroupByPath(destinationPath);
if (!destinationGroup) { 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; return EXIT_FAILURE;
} }
if (destinationGroup == entry->parent()) { 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; return EXIT_FAILURE;
} }
@ -70,11 +70,10 @@ int Move::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
outputTextStream << QObject::tr("Successfully moved entry %1 to group %2.").arg(entry->title(), destinationPath) out << QObject::tr("Successfully moved entry %1 to group %2.").arg(entry->title(), destinationPath) << endl;
<< endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -37,15 +37,13 @@ Remove::Remove()
int Remove::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Remove::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> 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); auto& entryPath = parser->positionalArguments().at(1);
TextStream outputTextStream(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QPointer<Entry> entry = database->rootGroup()->findEntryByPath(entryPath); QPointer<Entry> entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) { 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; return EXIT_FAILURE;
} }
@ -61,14 +59,14 @@ int Remove::executeWithDatabase(QSharedPointer<Database> database, QSharedPointe
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
if (recycled) { if (recycled) {
outputTextStream << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl; out << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl;
} else { } else {
outputTextStream << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl; out << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -41,21 +41,20 @@ RemoveGroup::~RemoveGroup()
int RemoveGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int RemoveGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
bool quiet = parser->isSet(Command::QuietOption); auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
QString groupPath = parser->positionalArguments().at(1); auto& err = Utils::STDERR;
TextStream outputTextStream(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly); QString groupPath = parser->positionalArguments().at(1);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
// Recursive option means were looking for a group to remove. // Recursive option means were looking for a group to remove.
QPointer<Group> group = database->rootGroup()->findGroupByPath(groupPath); QPointer<Group> group = database->rootGroup()->findGroupByPath(groupPath);
if (!group) { 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; return EXIT_FAILURE;
} }
if (group == database->rootGroup()) { 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; return EXIT_FAILURE;
} }
@ -70,14 +69,14 @@ int RemoveGroup::executeWithDatabase(QSharedPointer<Database> database, QSharedP
QString errorMessage; QString errorMessage;
if (!database->save(&errorMessage, true, false)) { 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; return EXIT_FAILURE;
} }
if (recycled) { if (recycled) {
outputTextStream << QObject::tr("Successfully recycled group %1.").arg(groupPath) << endl; out << QObject::tr("Successfully recycled group %1.").arg(groupPath) << endl;
} else { } else {
outputTextStream << QObject::tr("Successfully deleted group %1.").arg(groupPath) << endl; out << QObject::tr("Successfully deleted group %1.").arg(groupPath) << endl;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -59,8 +59,8 @@ Show::Show()
int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser) int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{ {
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); auto& out = Utils::STDOUT;
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1); const QString& entryPath = args.at(1);
@ -70,12 +70,12 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
Entry* entry = database->rootGroup()->findEntryByPath(entryPath); Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) { 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; return EXIT_FAILURE;
} }
if (showTotp && !entry->hasTotp()) { 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; return EXIT_FAILURE;
} }
@ -91,28 +91,28 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
QStringList attrs = Utils::findAttributes(*entry->attributes(), attributeName); QStringList attrs = Utils::findAttributes(*entry->attributes(), attributeName);
if (attrs.isEmpty()) { if (attrs.isEmpty()) {
encounteredError = true; encounteredError = true;
errorTextStream << QObject::tr("ERROR: unknown attribute %1.").arg(attributeName) << endl; err << QObject::tr("ERROR: unknown attribute %1.").arg(attributeName) << endl;
continue; continue;
} else if (attrs.size() > 1) { } else if (attrs.size() > 1) {
encounteredError = true; encounteredError = true;
errorTextStream << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.") err << QObject::tr("ERROR: attribute %1 is ambiguous, it matches %2.")
.arg(attributeName, QLocale().createSeparatedList(attrs)) .arg(attributeName, QLocale().createSeparatedList(attrs))
<< endl; << endl;
continue; continue;
} }
QString canonicalName = attrs[0]; QString canonicalName = attrs[0];
if (showDefaultAttributes) { if (showDefaultAttributes) {
outputTextStream << canonicalName << ": "; out << canonicalName << ": ";
} }
if (entry->attributes()->isProtected(canonicalName) && showDefaultAttributes && !showProtectedAttributes) { if (entry->attributes()->isProtected(canonicalName) && showDefaultAttributes && !showProtectedAttributes) {
outputTextStream << "PROTECTED" << endl; out << "PROTECTED" << endl;
} else { } else {
outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(canonicalName)) << endl; out << entry->resolveMultiplePlaceholders(entry->attributes()->value(canonicalName)) << endl;
} }
} }
if (showTotp) { if (showTotp) {
outputTextStream << entry->totp() << endl; out << entry->totp() << endl;
} }
return encounteredError ? EXIT_FAILURE : EXIT_SUCCESS; return encounteredError ? EXIT_FAILURE : EXIT_SUCCESS;

View File

@ -30,29 +30,33 @@
namespace Utils namespace Utils
{ {
/** QTextStream STDOUT;
* STDOUT file handle for the CLI. QTextStream STDERR;
*/ QTextStream STDIN;
FILE* STDOUT = stdout; QTextStream DEVNULL;
/** void setDefaultTextStreams()
* STDERR file handle for the CLI. {
*/ auto fd = new QFile();
FILE* STDERR = stderr; fd->open(stdout, QIODevice::WriteOnly);
STDOUT.setDevice(fd);
/** fd = new QFile();
* STDIN file handle for the CLI. fd->open(stderr, QIODevice::WriteOnly);
*/ STDERR.setDevice(fd);
FILE* STDIN = stdin;
/** fd = new QFile();
* DEVNULL file handle for the CLI. fd->open(stdin, QIODevice::ReadOnly);
*/ STDIN.setDevice(fd);
fd = new QFile();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
FILE* DEVNULL = fopen("nul", "w"); fd->open(fopen("nul", "w"), QIODevice::WriteOnly);
#else #else
FILE* DEVNULL = fopen("/dev/null", "w"); fd->open(fopen("/dev/null", "w"), QIODevice::WriteOnly);
#endif #endif
DEVNULL.setDevice(fd);
}
void setStdinEcho(bool enable = true) void setStdinEcho(bool enable = true)
{ {
@ -82,36 +86,14 @@ namespace Utils
#endif #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<Database> unlockDatabase(const QString& databaseFilename, QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected, const bool isPasswordProtected,
const QString& keyFilename, const QString& keyFilename,
const QString& yubiKeySlot, const QString& yubiKeySlot,
FILE* outputDescriptor, bool quiet)
FILE* errorDescriptor)
{ {
auto& err = quiet ? DEVNULL : STDERR;
auto compositeKey = QSharedPointer<CompositeKey>::create(); auto compositeKey = QSharedPointer<CompositeKey>::create();
TextStream out(outputDescriptor);
TextStream err(errorDescriptor);
QFileInfo dbFileInfo(databaseFilename); QFileInfo dbFileInfo(databaseFilename);
if (dbFileInfo.canonicalFilePath().isEmpty()) { if (dbFileInfo.canonicalFilePath().isEmpty()) {
@ -130,8 +112,8 @@ namespace Utils
} }
if (isPasswordProtected) { if (isPasswordProtected) {
out << QObject::tr("Enter password to unlock %1: ").arg(databaseFilename) << flush; err << QObject::tr("Enter password to unlock %1: ").arg(databaseFilename) << flush;
QString line = Utils::getPassword(outputDescriptor); QString line = Utils::getPassword(quiet);
auto passwordKey = QSharedPointer<PasswordKey>::create(); auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line); passwordKey->setPassword(line);
compositeKey->addKey(passwordKey); compositeKey->addKey(passwordKey);
@ -177,7 +159,7 @@ namespace Utils
slot, slot,
blocking, blocking,
QObject::tr("Please touch the button on your YubiKey to unlock %1").arg(databaseFilename), QObject::tr("Please touch the button on your YubiKey to unlock %1").arg(databaseFilename),
outputDescriptor)); err.device()));
compositeKey->addChallengeResponseKey(key); compositeKey->addChallengeResponseKey(key);
} }
#else #else
@ -200,19 +182,10 @@ namespace Utils
* *
* @return the password * @return the password
*/ */
QString getPassword(FILE* outputDescriptor) QString getPassword(bool quiet)
{ {
TextStream out(outputDescriptor, QIODevice::WriteOnly); auto& in = STDIN;
auto& out = quiet ? DEVNULL : STDERR;
// 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);
setStdinEcho(false); setStdinEcho(false);
QString line = in.readLine(); QString line = in.readLine();
@ -228,37 +201,34 @@ namespace Utils
* @return Pointer to the PasswordKey or null if passwordkey is skipped * @return Pointer to the PasswordKey or null if passwordkey is skipped
* by user * by user
*/ */
QSharedPointer<PasswordKey> getPasswordFromStdin() QSharedPointer<PasswordKey> getConfirmedPassword()
{ {
QSharedPointer<PasswordKey> passwordKey; auto& err = STDERR;
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); auto& in = STDIN;
out << QObject::tr("Enter password to encrypt database (optional): "); QSharedPointer<PasswordKey> passwordKey;
out.flush();
err << QObject::tr("Enter password to encrypt database (optional): ");
err.flush();
auto password = Utils::getPassword(); auto password = Utils::getPassword();
if (password.isEmpty()) { if (password.isEmpty()) {
out << QObject::tr("Do you want to create a database with an empty password? [y/N]: "); err << QObject::tr("Do you want to create a database with an empty password? [y/N]: ");
out.flush(); err.flush();
TextStream ts(STDIN, QIODevice::ReadOnly); auto ans = in.readLine();
if (!ts.device()->isSequential()) {
// This is required for testing on macOS
ts.seek(0);
}
auto ans = ts.readLine();
if (ans.toLower().startsWith("y")) { if (ans.toLower().startsWith("y")) {
passwordKey = QSharedPointer<PasswordKey>::create(""); passwordKey = QSharedPointer<PasswordKey>::create("");
} }
out << endl; err << endl;
} else { } else {
out << QObject::tr("Repeat password: "); err << QObject::tr("Repeat password: ");
out.flush(); err.flush();
auto repeat = Utils::getPassword(); auto repeat = Utils::getPassword();
if (password == repeat) { if (password == repeat) {
passwordKey = QSharedPointer<PasswordKey>::create(password); passwordKey = QSharedPointer<PasswordKey>::create(password);
} else { } 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) int clipText(const QString& text)
{ {
TextStream err(Utils::STDERR); auto& err = STDERR;
// List of programs and their arguments // List of programs and their arguments
QList<QPair<QString, QString>> clipPrograms; QList<QPair<QString, QString>> clipPrograms;

View File

@ -34,21 +34,22 @@
namespace Utils namespace Utils
{ {
extern FILE* STDOUT; extern QTextStream STDOUT;
extern FILE* STDERR; extern QTextStream STDERR;
extern FILE* STDIN; extern QTextStream STDIN;
extern FILE* DEVNULL; extern QTextStream DEVNULL;
void setDefaultTextStreams();
void setStdinEcho(bool enable); void setStdinEcho(bool enable);
QString getPassword(FILE* outputDescriptor = STDOUT); QString getPassword(bool quiet = false);
QSharedPointer<PasswordKey> getPasswordFromStdin(); QSharedPointer<PasswordKey> getConfirmedPassword();
int clipText(const QString& text); int clipText(const QString& text);
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename, QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected = true, const bool isPasswordProtected = true,
const QString& keyFilename = {}, const QString& keyFilename = {},
const QString& yubiKeySlot = {}, const QString& yubiKeySlot = {},
FILE* outputDescriptor = STDOUT, bool quiet = false);
FILE* errorDescriptor = STDERR);
QStringList splitCommandString(const QString& command); QStringList splitCommandString(const QString& command);
@ -59,11 +60,6 @@ namespace Utils
* (case-insensitive). * (case-insensitive).
*/ */
QStringList findAttributes(const EntryAttributes& attributes, const QString& name); QStringList findAttributes(const EntryAttributes& attributes, const QString& name);
namespace Test
{
void setNextPassword(const QString& password, bool repeat = false);
}
}; // namespace Utils }; // namespace Utils
#endif // KEEPASSXC_UTILS_H #endif // KEEPASSXC_UTILS_H

View File

@ -118,13 +118,14 @@ private:
void enterInteractiveMode(const QStringList& arguments) void enterInteractiveMode(const QStringList& arguments)
{ {
auto& err = Utils::STDERR;
// Replace command list with interactive version // Replace command list with interactive version
Commands::setupCommands(true); Commands::setupCommands(true);
Open o; Open openCmd;
QStringList openArgs(arguments); QStringList openArgs(arguments);
openArgs.removeFirst(); openArgs.removeFirst();
o.execute(openArgs); openCmd.execute(openArgs);
QScopedPointer<LineReader> reader; QScopedPointer<LineReader> reader;
#if defined(USE_READLINE) #if defined(USE_READLINE)
@ -133,12 +134,10 @@ void enterInteractiveMode(const QStringList& arguments)
reader.reset(new SimpleLineReader()); reader.reset(new SimpleLineReader());
#endif #endif
QSharedPointer<Database> currentDatabase(o.currentDatabase); QSharedPointer<Database> currentDatabase(openCmd.currentDatabase);
QString command; QString command;
while (true) { while (true) {
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QString prompt; QString prompt;
if (currentDatabase) { if (currentDatabase) {
prompt += currentDatabase->metadata()->name(); prompt += currentDatabase->metadata()->name();
@ -159,7 +158,7 @@ void enterInteractiveMode(const QStringList& arguments)
auto cmd = Commands::getCommand(args[0]); auto cmd = Commands::getCommand(args[0]);
if (!cmd) { if (!cmd) {
errorTextStream << QObject::tr("Unknown command %1").arg(args[0]) << "\n"; err << QObject::tr("Unknown command %1").arg(args[0]) << "\n";
continue; continue;
} else if (cmd->name == "quit" || cmd->name == "exit") { } else if (cmd->name == "quit" || cmd->name == "exit") {
break; break;
@ -186,10 +185,12 @@ int main(int argc, char** argv)
QCoreApplication::setApplicationVersion(KEEPASSXC_VERSION); QCoreApplication::setApplicationVersion(KEEPASSXC_VERSION);
Bootstrap::bootstrap(); Bootstrap::bootstrap();
Utils::setDefaultTextStreams();
Commands::setupCommands(false); Commands::setupCommands(false);
TextStream out(stdout); auto& out = Utils::STDOUT;
TextStream err(stderr); auto& err = Utils::STDERR;
QStringList arguments; QStringList arguments;
for (int i = 0; i < argc; ++i) { for (int i = 0; i < argc; ++i) {
arguments << QString(argv[i]); arguments << QString(argv[i]);

View File

@ -77,8 +77,7 @@ protected:
{ {
Q_ASSERT(static_cast<unsigned long>(data.size()) < (1ull << (sizeof(SizedQInt) * 8))); Q_ASSERT(static_cast<unsigned long>(data.size()) < (1ull << (sizeof(SizedQInt) * 8)));
QByteArray fieldIdArr; QByteArray fieldIdArr(1, static_cast<char>(fieldId));
fieldIdArr[0] = static_cast<char>(fieldId);
CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
CHECK_RETURN_FALSE(writeData( CHECK_RETURN_FALSE(writeData(
device, Endian::sizedIntToBytes<SizedQInt>(static_cast<SizedQInt>(data.size()), KeePass2::BYTEORDER))); device, Endian::sizedIntToBytes<SizedQInt>(static_cast<SizedQInt>(data.size()), KeePass2::BYTEORDER)));

View File

@ -30,13 +30,13 @@ QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");
YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(int slot, YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(int slot,
bool blocking, bool blocking,
QString messageInteraction, QString messageInteraction,
FILE* outputDescriptor) QIODevice* out)
: ChallengeResponseKey(UUID) : ChallengeResponseKey(UUID)
, m_slot(slot) , m_slot(slot)
, m_blocking(blocking) , m_blocking(blocking)
, m_messageInteraction(messageInteraction) , m_messageInteraction(messageInteraction)
, m_out(outputDescriptor)
{ {
m_out.setDevice(out);
} }
QByteArray YkChallengeResponseKeyCLI::rawKey() const QByteArray YkChallengeResponseKeyCLI::rawKey() const
@ -54,12 +54,11 @@ bool YkChallengeResponseKeyCLI::challenge(const QByteArray& c)
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge, unsigned int retries) bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge, unsigned int retries)
{ {
QTextStream out(m_out, QIODevice::WriteOnly);
do { do {
--retries; --retries;
if (m_blocking) { 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); YubiKey::ChallengeResult result = YubiKey::instance()->challenge(m_slot, m_blocking, challenge, m_key);
if (result == YubiKey::SUCCESS) { if (result == YubiKey::SUCCESS) {

View File

@ -23,6 +23,7 @@
#include "keys/drivers/YubiKey.h" #include "keys/drivers/YubiKey.h"
#include <QObject> #include <QObject>
#include <QSharedPointer>
#include <QTextStream> #include <QTextStream>
class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
@ -32,7 +33,7 @@ class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
public: public:
static QUuid UUID; 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; QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override; bool challenge(const QByteArray& challenge) override;
@ -43,7 +44,7 @@ private:
int m_slot; int m_slot;
bool m_blocking; bool m_blocking;
QString m_messageInteraction; QString m_messageInteraction;
FILE* m_out; QTextStream m_out;
}; };
#endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H #endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H

File diff suppressed because it is too large Load Diff

View File

@ -18,30 +18,33 @@
#ifndef KEEPASSXC_TESTCLI_H #ifndef KEEPASSXC_TESTCLI_H
#define KEEPASSXC_TESTCLI_H #define KEEPASSXC_TESTCLI_H
#include "core/Database.h" #include <QBuffer>
#include "util/TemporaryFile.h"
#include <QByteArray>
#include <QFile>
#include <QScopedPointer> #include <QScopedPointer>
#include <QSharedPointer> #include <QSharedPointer>
#include <QTemporaryFile> #include <QStringList>
#include <QTest> #include <QTest>
#include <stdio.h> #include "util/TemporaryFile.h"
class Command;
class Database;
class TestCli : public QObject class TestCli : public QObject
{ {
Q_OBJECT Q_OBJECT
private: private:
QSharedPointer<Database> readTestDatabase() const; QSharedPointer<Database>
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: private slots:
void initTestCase(); void initTestCase();
void init(); void init();
void cleanup(); void cleanup();
void cleanupTestCase();
void testBatchCommands(); void testBatchCommands();
void testAdd(); void testAdd();
@ -78,21 +81,16 @@ private slots:
void testYubiKeyOption(); void testYubiKeyOption();
private: private:
QByteArray m_dbData;
QByteArray m_dbData2;
QByteArray m_xmlData;
QByteArray m_yubiKeyProtectedDbData;
QByteArray m_keyFileProtectedDbData;
QByteArray m_keyFileProtectedNoPasswordDbData;
QScopedPointer<TemporaryFile> m_dbFile; QScopedPointer<TemporaryFile> m_dbFile;
QScopedPointer<TemporaryFile> m_dbFile2; QScopedPointer<TemporaryFile> m_dbFile2;
QScopedPointer<TemporaryFile> m_xmlFile; QScopedPointer<TemporaryFile> m_xmlFile;
QScopedPointer<TemporaryFile> m_keyFileProtectedDbFile; QScopedPointer<TemporaryFile> m_keyFileProtectedDbFile;
QScopedPointer<TemporaryFile> m_keyFileProtectedNoPasswordDbFile; QScopedPointer<TemporaryFile> m_keyFileProtectedNoPasswordDbFile;
QScopedPointer<TemporaryFile> m_yubiKeyProtectedDbFile; QScopedPointer<TemporaryFile> m_yubiKeyProtectedDbFile;
QScopedPointer<TemporaryFile> m_stdoutFile;
QScopedPointer<TemporaryFile> m_stderrFile; QScopedPointer<QBuffer> m_stdout;
QScopedPointer<TemporaryFile> m_stdinFile; QScopedPointer<QBuffer> m_stderr;
QScopedPointer<QBuffer> m_stdin;
}; };
#endif // KEEPASSXC_TESTCLI_H #endif // KEEPASSXC_TESTCLI_H