CLI Command cleanup

This PR cleans up the `Command` classes in the CLI, introducing a
`DatabaseCommand` class for the commands operating on a database,
and a `getCommandLineParser` command to centralize the arguments
parsing and validation.

The opening of the database based on the CLI arguments and options
is now centralized in `DatabaseCommand.execute`, making it easy to
add new database opening features (like YubiKey support for the CLI).

Also a couple of bugs fixed:
  * `Create` was still using `stdout` for some error messages.
  * `Diceware` and `Generate` were not validating that the word count was an integer.
  * `Diceware` was also using `stdout` for some error messages.
This commit is contained in:
louib 2019-06-01 17:53:40 -04:00 committed by Jonathan White
parent 3cf171cbf5
commit 04360ed552
31 changed files with 591 additions and 637 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,8 +20,6 @@
#include "Add.h"
#include <QCommandLineParser>
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
@ -29,106 +27,84 @@
#include "core/Group.h"
#include "core/PasswordGenerator.h"
const QCommandLineOption Add::UsernameOption = QCommandLineOption(QStringList() << "u"
<< "username",
QObject::tr("Username for the entry."),
QObject::tr("username"));
const QCommandLineOption Add::UrlOption =
QCommandLineOption(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
const QCommandLineOption Add::PasswordPromptOption =
QCommandLineOption(QStringList() << "p"
<< "password-prompt",
QObject::tr("Prompt for the entry's password."));
const QCommandLineOption Add::GenerateOption = QCommandLineOption(QStringList() << "g"
<< "generate",
QObject::tr("Generate a password for the entry."));
const QCommandLineOption Add::PasswordLengthOption =
QCommandLineOption(QStringList() << "l"
<< "password-length",
QObject::tr("Length for the generated password."),
QObject::tr("length"));
Add::Add()
{
name = QString("add");
description = QObject::tr("Add a new entry to a database.");
options.append(Add::UsernameOption);
options.append(Add::UrlOption);
options.append(Add::PasswordPromptOption);
options.append(Add::GenerateOption);
options.append(Add::PasswordLengthOption);
positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to add."), QString("")});
}
Add::~Add()
{
}
int Add::execute(const QStringList& arguments)
int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly);
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption username(QStringList() << "u"
<< "username",
QObject::tr("Username for the entry."),
QObject::tr("username"));
parser.addOption(username);
QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
parser.addOption(url);
QCommandLineOption prompt(QStringList() << "p"
<< "password-prompt",
QObject::tr("Prompt for the entry's password."));
parser.addOption(prompt);
QCommandLineOption generate(QStringList() << "g"
<< "generate",
QObject::tr("Generate a password for the entry."));
parser.addOption(generate);
QCommandLineOption length(QStringList() << "l"
<< "password-length",
QObject::tr("Length for the generated password."),
QObject::tr("length"));
parser.addOption(length);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to add."));
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "add [options]");
return EXIT_FAILURE;
}
const QStringList args = parser->positionalArguments();
const QString& databasePath = args.at(0);
const QString& entryPath = args.at(1);
auto db = Utils::unlockDatabase(databasePath,
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
// Validating the password length here, before we actually create
// the entry.
QString passwordLength = parser.value(length);
QString passwordLength = parser->value(Add::PasswordLengthOption);
if (!passwordLength.isEmpty() && !passwordLength.toInt()) {
errorTextStream << QObject::tr("Invalid value for password length %1.").arg(passwordLength) << endl;
return EXIT_FAILURE;
}
Entry* entry = db->rootGroup()->addEntryWithPath(entryPath);
Entry* entry = database->rootGroup()->addEntryWithPath(entryPath);
if (!entry) {
errorTextStream << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl;
return EXIT_FAILURE;
}
if (!parser.value("username").isEmpty()) {
entry->setUsername(parser.value("username"));
if (!parser->value(Add::UsernameOption).isEmpty()) {
entry->setUsername(parser->value(Add::UsernameOption));
}
if (!parser.value("url").isEmpty()) {
entry->setUrl(parser.value("url"));
if (!parser->value(Add::UrlOption).isEmpty()) {
entry->setUrl(parser->value(Add::UrlOption));
}
if (parser.isSet(prompt)) {
if (!parser.isSet(Command::QuietOption)) {
if (parser->isSet(Add::PasswordPromptOption)) {
if (!parser->isSet(Command::QuietOption)) {
outputTextStream << 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) ? Utils::DEVNULL : Utils::STDOUT);
entry->setPassword(password);
} else if (parser.isSet(generate)) {
} else if (parser->isSet(Add::GenerateOption)) {
PasswordGenerator passwordGenerator;
if (passwordLength.isEmpty()) {
@ -144,12 +120,12 @@ int Add::execute(const QStringList& arguments)
}
QString errorMessage;
if (!db->save(databasePath, &errorMessage, true, false)) {
if (!database->save(databasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (!parser.isSet(Command::QuietOption)) {
if (!parser->isSet(Command::QuietOption)) {
outputTextStream << QObject::tr("Successfully added entry %1.").arg(entry->title()) << endl;
}
return EXIT_SUCCESS;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,14 +18,21 @@
#ifndef KEEPASSXC_ADD_H
#define KEEPASSXC_ADD_H
#include "Command.h"
#include "DatabaseCommand.h"
class Add : public Command
class Add : public DatabaseCommand
{
public:
Add();
~Add();
int execute(const QStringList& arguments) override;
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
static const QCommandLineOption UsernameOption;
static const QCommandLineOption UrlOption;
static const QCommandLineOption PasswordPromptOption;
static const QCommandLineOption GenerateOption;
static const QCommandLineOption PasswordLengthOption;
};
#endif // KEEPASSXC_ADD_H

View File

@ -1,4 +1,4 @@
# Copyright (C) 2017 KeePassXC Team
# Copyright (C) 2019 KeePassXC Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -18,6 +18,7 @@ set(cli_SOURCES
Clip.cpp
Create.cpp
Command.cpp
DatabaseCommand.cpp
Diceware.cpp
Edit.cpp
Estimate.cpp

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -22,80 +22,52 @@
#include "Clip.h"
#include <QCommandLineParser>
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
const QCommandLineOption Clip::TotpOption = QCommandLineOption(QStringList() << "t"
<< "totp",
QObject::tr("Copy the current TOTP to the clipboard."));
Clip::Clip()
{
name = QString("clip");
description = QObject::tr("Copy an entry's password to the clipboard.");
options.append(Clip::TotpOption);
positionalArguments.append(
{QString("entry"), QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"), QString("")});
optionalArguments.append(
{QString("timeout"), QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]")});
}
Clip::~Clip()
{
}
int Clip::execute(const QStringList& arguments)
int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption totp(QStringList() << "t"
<< "totp",
QObject::tr("Copy the current TOTP to the clipboard."));
parser.addOption(totp);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"));
parser.addPositionalArgument(
"timeout", QObject::tr("Timeout in seconds before clearing the clipboard."), "[timeout]");
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2 && args.size() != 3) {
errorTextStream << parser.helpText().replace("[options]", "clip [options]");
return EXIT_FAILURE;
const QStringList args = parser->positionalArguments();
QString entryPath = args.at(1);
QString timeout;
if (args.size() == 3) {
timeout = args.at(2);
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
return clipEntry(db, args.at(1), args.value(2), parser.isSet(totp), parser.isSet(Command::QuietOption));
}
int Clip::clipEntry(const QSharedPointer<Database>& database,
const QString& entryPath,
const QString& timeout,
bool clipTotp,
bool silent)
{
bool clipTotp = parser->isSet(Clip::TotpOption);
TextStream errorTextStream(Utils::STDERR);
int timeoutSeconds = 0;
if (!timeout.isEmpty() && !timeout.toInt()) {
if (!timeout.isEmpty() && timeout.toInt() <= 0) {
errorTextStream << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl;
return EXIT_FAILURE;
} else if (!timeout.isEmpty()) {
timeoutSeconds = timeout.toInt();
}
TextStream outputTextStream(silent ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly);
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;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,19 +18,17 @@
#ifndef KEEPASSXC_CLIP_H
#define KEEPASSXC_CLIP_H
#include "Command.h"
#include "DatabaseCommand.h"
class Clip : public Command
class Clip : public DatabaseCommand
{
public:
Clip();
~Clip();
int execute(const QStringList& arguments) override;
int clipEntry(const QSharedPointer<Database>& database,
const QString& entryPath,
const QString& timeout,
bool clipTotp,
bool silent);
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
static const QCommandLineOption TotpOption;
};
#endif // KEEPASSXC_CLIP_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -35,6 +35,8 @@
#include "Merge.h"
#include "Remove.h"
#include "Show.h"
#include "TextStream.h"
#include "Utils.h"
const QCommandLineOption Command::QuietOption =
QCommandLineOption(QStringList() << "q"
@ -51,13 +53,17 @@ const QCommandLineOption Command::NoPasswordOption =
QMap<QString, Command*> commands;
Command::Command()
{
options.append(Command::QuietOption);
}
Command::~Command()
{
}
QString Command::getDescriptionLine()
{
QString response = name;
QString space(" ");
QString spaces = space.repeated(15 - name.length());
@ -67,6 +73,36 @@ QString Command::getDescriptionLine()
return response;
}
QSharedPointer<QCommandLineParser> Command::getCommandLineParser(const QStringList& arguments)
{
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QSharedPointer<QCommandLineParser> parser = QSharedPointer<QCommandLineParser>(new QCommandLineParser());
parser->setApplicationDescription(description);
for (CommandLineArgument positionalArgument : positionalArguments) {
parser->addPositionalArgument(
positionalArgument.name, positionalArgument.description, positionalArgument.syntax);
}
for (CommandLineArgument optionalArgument : optionalArguments) {
parser->addPositionalArgument(optionalArgument.name, optionalArgument.description, optionalArgument.syntax);
}
for (QCommandLineOption option : options) {
parser->addOption(option);
}
parser->addHelpOption();
parser->process(arguments);
if (parser->positionalArguments().size() < positionalArguments.size()) {
errorTextStream << parser->helpText().replace("[options]", name.append(" [options]"));
return QSharedPointer<QCommandLineParser>(nullptr);
}
if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) {
errorTextStream << parser->helpText().replace("[options]", name.append(" [options]"));
return QSharedPointer<QCommandLineParser>(nullptr);
}
return parser;
}
void populateCommands()
{
if (commands.isEmpty()) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,6 +19,7 @@
#define KEEPASSXC_COMMAND_H
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QList>
#include <QObject>
#include <QString>
@ -26,14 +27,28 @@
#include "core/Database.h"
// At the moment, there's no QT class for the positional arguments
// like there is for the options (QCommandLineOption).
struct CommandLineArgument
{
QString name;
QString description;
QString syntax;
};
class Command
{
public:
Command();
virtual ~Command();
virtual int execute(const QStringList& arguments) = 0;
QString name;
QString description;
QList<CommandLineArgument> positionalArguments;
QList<CommandLineArgument> optionalArguments;
QList<QCommandLineOption> options;
QString getDescriptionLine();
QSharedPointer<QCommandLineParser> getCommandLineParser(const QStringList& arguments);
static QList<Command*> getCommands();
static Command* getCommand(const QString& commandName);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,7 +18,6 @@
#include <cstdlib>
#include <stdio.h>
#include <QCommandLineParser>
#include <QFileInfo>
#include <QString>
#include <QTextStream>
@ -35,6 +34,8 @@ Create::Create()
{
name = QString("create");
description = QObject::tr("Create a new database.");
positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")});
options.append(Command::KeyFileOption);
}
Create::~Create()
@ -59,21 +60,13 @@ int Create::execute(const QStringList& arguments)
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::KeyFileOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() < 1) {
out << parser.helpText().replace("[options]", "create [options]");
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}
const QStringList args = parser->positionalArguments();
const QString& databaseFilename = args.at(0);
if (QFileInfo::exists(databaseFilename)) {
err << QObject::tr("File %1 already exists.").arg(databaseFilename) << endl;
@ -88,8 +81,8 @@ int Create::execute(const QStringList& arguments)
}
QSharedPointer<FileKey> fileKey;
if (parser.isSet(Command::KeyFileOption)) {
if (!loadFileKey(parser.value(Command::KeyFileOption), fileKey)) {
if (parser->isSet(Command::KeyFileOption)) {
if (!loadFileKey(parser->value(Command::KeyFileOption), fileKey)) {
err << QObject::tr("Loading the key file failed") << endl;
return EXIT_FAILURE;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DatabaseCommand.h"
#include "Utils.h"
DatabaseCommand::DatabaseCommand()
{
positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")});
options.append(Command::KeyFileOption);
options.append(Command::NoPasswordOption);
}
int DatabaseCommand::execute(const QStringList& arguments)
{
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}
const QStringList args = parser->positionalArguments();
auto db = Utils::unlockDatabase(args.at(0),
!parser->isSet(Command::NoPasswordOption),
parser->value(Command::KeyFileOption),
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
return executeWithDatabase(db, parser);
}

35
src/cli/DatabaseCommand.h Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_DATABASECOMMAND_H
#define KEEPASSXC_DATABASECOMMAND_H
#include <QCommandLineOption>
#include "Command.h"
#include "Utils.h"
#include "core/Database.h"
class DatabaseCommand : public Command
{
public:
DatabaseCommand();
int execute(const QStringList& arguments) override;
virtual int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) = 0;
};
#endif // KEEPASSXC_DATABASECOMMAND_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,16 +20,28 @@
#include "Diceware.h"
#include <QCommandLineParser>
#include "Utils.h"
#include "cli/TextStream.h"
#include "core/PassphraseGenerator.h"
const QCommandLineOption Diceware::WordCountOption =
QCommandLineOption(QStringList() << "W"
<< "words",
QObject::tr("Word count for the diceware passphrase."),
QObject::tr("count", "CLI parameter"));
const QCommandLineOption Diceware::WordListOption =
QCommandLineOption(QStringList() << "w"
<< "word-list",
QObject::tr("Wordlist for the diceware generator.\n[Default: EFF English]"),
QObject::tr("path"));
Diceware::Diceware()
{
name = QString("diceware");
description = QObject::tr("Generate a new random diceware passphrase.");
options.append(Diceware::WordCountOption);
options.append(Diceware::WordListOption);
}
Diceware::~Diceware()
@ -38,45 +50,35 @@ Diceware::~Diceware()
int Diceware::execute(const QStringList& arguments)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
QCommandLineOption words(QStringList() << "W"
<< "words",
QObject::tr("Word count for the diceware passphrase."),
QObject::tr("count", "CLI parameter"));
parser.addOption(words);
QCommandLineOption wordlistFile(QStringList() << "w"
<< "word-list",
QObject::tr("Wordlist for the diceware generator.\n[Default: EFF English]"),
QObject::tr("path"));
parser.addOption(wordlistFile);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) {
errorTextStream << parser.helpText().replace("[options]", "diceware [options]");
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
PassphraseGenerator dicewareGenerator;
if (parser.value(words).isEmpty()) {
QString wordCount = parser->value(Diceware::WordCountOption);
if (wordCount.isEmpty()) {
dicewareGenerator.setWordCount(PassphraseGenerator::DefaultWordCount);
} else if (wordCount.toInt() <= 0) {
errorTextStream << QObject::tr("Invalid word count %1").arg(wordCount) << endl;
return EXIT_FAILURE;
} else {
int wordcount = parser.value(words).toInt();
dicewareGenerator.setWordCount(wordcount);
dicewareGenerator.setWordCount(wordCount.toInt());
}
if (!parser.value(wordlistFile).isEmpty()) {
dicewareGenerator.setWordList(parser.value(wordlistFile));
QString wordListFile = parser->value(Diceware::WordListOption);
if (!wordListFile.isEmpty()) {
dicewareGenerator.setWordList(wordListFile);
}
if (!dicewareGenerator.isValid()) {
outputTextStream << parser.helpText().replace("[options]", "diceware [options]");
// 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;
return EXIT_FAILURE;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,7 +25,11 @@ class Diceware : public Command
public:
Diceware();
~Diceware();
int execute(const QStringList& arguments) override;
int execute(const QStringList& arguments);
static const QCommandLineOption WordCountOption;
static const QCommandLineOption WordListOption;
};
#endif // KEEPASSXC_DICEWARE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,8 +20,7 @@
#include "Edit.h"
#include <QCommandLineParser>
#include "cli/Add.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
@ -29,120 +28,80 @@
#include "core/Group.h"
#include "core/PasswordGenerator.h"
const QCommandLineOption Edit::TitleOption = QCommandLineOption(QStringList() << "t"
<< "title",
QObject::tr("Title for the entry."),
QObject::tr("title"));
Edit::Edit()
{
name = QString("edit");
description = QObject::tr("Edit an entry.");
// Using some of the options from the Add command since they are the same.
options.append(Add::UsernameOption);
options.append(Add::UrlOption);
options.append(Add::PasswordPromptOption);
options.append(Add::GenerateOption);
options.append(Add::PasswordLengthOption);
options.append(Edit::TitleOption);
positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to edit."), QString("")});
}
Edit::~Edit()
{
}
int Edit::execute(const QStringList& arguments)
int Edit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption username(QStringList() << "u"
<< "username",
QObject::tr("Username for the entry."),
QObject::tr("username"));
parser.addOption(username);
QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
parser.addOption(url);
QCommandLineOption title(QStringList() << "t"
<< "title",
QObject::tr("Title for the entry."),
QObject::tr("title"));
parser.addOption(title);
QCommandLineOption prompt(QStringList() << "p"
<< "password-prompt",
QObject::tr("Prompt for the entry's password."));
parser.addOption(prompt);
QCommandLineOption generate(QStringList() << "g"
<< "generate",
QObject::tr("Generate a password for the entry."));
parser.addOption(generate);
QCommandLineOption length(QStringList() << "l"
<< "password-length",
QObject::tr("Length for the generated password."),
QObject::tr("length"));
parser.addOption(length);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to edit."));
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "edit [options]");
return EXIT_FAILURE;
}
const QStringList args = parser->positionalArguments();
const QString& databasePath = args.at(0);
const QString& entryPath = args.at(1);
auto db = Utils::unlockDatabase(databasePath,
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
QString passwordLength = parser.value(length);
QString passwordLength = parser->value(Add::PasswordLengthOption);
if (!passwordLength.isEmpty() && !passwordLength.toInt()) {
errorTextStream << QObject::tr("Invalid value for password length: %1").arg(passwordLength) << endl;
return EXIT_FAILURE;
}
Entry* entry = db->rootGroup()->findEntryByPath(entryPath);
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) {
errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl;
return EXIT_FAILURE;
}
if (parser.value("username").isEmpty() && parser.value("url").isEmpty() && parser.value("title").isEmpty()
&& !parser.isSet(prompt) && !parser.isSet(generate)) {
QString username = parser->value(Add::UsernameOption);
QString url = parser->value(Add::UrlOption);
QString title = parser->value(Edit::TitleOption);
bool generate = parser->isSet(Add::GenerateOption);
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;
return EXIT_FAILURE;
}
entry->beginUpdate();
if (!parser.value("title").isEmpty()) {
entry->setTitle(parser.value("title"));
if (!title.isEmpty()) {
entry->setTitle(title);
}
if (!parser.value("username").isEmpty()) {
entry->setUsername(parser.value("username"));
if (!username.isEmpty()) {
entry->setUsername(username);
}
if (!parser.value("url").isEmpty()) {
entry->setUrl(parser.value("url"));
if (!url.isEmpty()) {
entry->setUrl(url);
}
if (parser.isSet(prompt)) {
if (!parser.isSet(Command::QuietOption)) {
outputTextStream << QObject::tr("Enter new password for entry: ") << flush;
}
QString password = Utils::getPassword(parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT);
if (prompt) {
outputTextStream << QObject::tr("Enter new password for entry: ") << flush;
QString password = Utils::getPassword(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT);
entry->setPassword(password);
} else if (parser.isSet(generate)) {
} else if (generate) {
PasswordGenerator passwordGenerator;
if (passwordLength.isEmpty()) {
@ -160,13 +119,11 @@ int Edit::execute(const QStringList& arguments)
entry->endUpdate();
QString errorMessage;
if (!db->save(databasePath, &errorMessage, true, false)) {
if (!database->save(databasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (!parser.isSet(Command::QuietOption)) {
outputTextStream << QObject::tr("Successfully edited entry %1.").arg(entry->title()) << endl;
}
outputTextStream << QObject::tr("Successfully edited entry %1.").arg(entry->title()) << endl;
return EXIT_SUCCESS;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,14 +18,16 @@
#ifndef KEEPASSXC_EDIT_H
#define KEEPASSXC_EDIT_H
#include "Command.h"
#include "DatabaseCommand.h"
class Edit : public Command
class Edit : public DatabaseCommand
{
public:
Edit();
~Edit();
int execute(const QStringList& arguments) override;
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
static const QCommandLineOption TitleOption;
};
#endif // KEEPASSXC_EDIT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,8 +18,6 @@
#include "Estimate.h"
#include "cli/Utils.h"
#include <QCommandLineParser>
#include "cli/TextStream.h"
#include <stdio.h>
#include <stdlib.h>
@ -33,9 +31,17 @@
#endif
#endif
const QCommandLineOption Estimate::AdvancedOption =
QCommandLineOption(QStringList() << "a"
<< "advanced",
QObject::tr("Perform advanced analysis on the password."));
Estimate::Estimate()
{
name = QString("estimate");
optionalArguments.append(
{QString("password"), QObject::tr("Password for which to estimate the entropy."), QString("[password]")});
options.append(Estimate::AdvancedOption);
description = QObject::tr("Estimate the entropy of a password.");
}
@ -156,25 +162,14 @@ static void estimate(const char* pwd, bool advanced)
int Estimate::execute(const QStringList& arguments)
{
TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("password", QObject::tr("Password for which to estimate the entropy."), "[password]");
QCommandLineOption advancedOption(QStringList() << "a"
<< "advanced",
QObject::tr("Perform advanced analysis on the password."));
parser.addOption(advancedOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() > 1) {
errorTextStream << parser.helpText().replace("[options]", "estimate [options]");
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}
TextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly);
const QStringList args = parser->positionalArguments();
QString password;
if (args.size() == 1) {
password = args.at(0);
@ -182,6 +177,6 @@ int Estimate::execute(const QStringList& arguments)
password = inputTextStream.readLine();
}
estimate(password.toLatin1(), parser.isSet(advancedOption));
estimate(password.toLatin1(), parser->isSet(Estimate::AdvancedOption));
return EXIT_SUCCESS;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -26,6 +26,8 @@ public:
Estimate();
~Estimate();
int execute(const QStringList& arguments) override;
static const QCommandLineOption AdvancedOption;
};
#endif // KEEPASSXC_ESTIMATE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,9 +20,6 @@
#include "Extract.h"
#include <QCommandLineParser>
#include <QFile>
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
@ -37,40 +34,14 @@ Extract::~Extract()
{
}
int Extract::execute(const QStringList& arguments)
int Extract::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser>)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database to extract."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 1) {
errorTextStream << parser.helpText().replace("[options]", "extract [options]");
return EXIT_FAILURE;
}
auto compositeKey = QSharedPointer<CompositeKey>::create();
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
QByteArray xmlData;
QString errorMessage;
if (!db->extract(xmlData, &errorMessage)) {
if (!database->extract(xmlData, &errorMessage)) {
errorTextStream << QObject::tr("Unable to extract database %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,14 +18,15 @@
#ifndef KEEPASSXC_EXTRACT_H
#define KEEPASSXC_EXTRACT_H
#include "Command.h"
#include "DatabaseCommand.h"
class Extract : public Command
class Extract : public DatabaseCommand
{
public:
Extract();
~Extract();
int execute(const QStringList& arguments) override;
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
};
#endif // KEEPASSXC_EXTRACT_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,17 +19,60 @@
#include <stdio.h>
#include "Generate.h"
#include "cli/Utils.h"
#include <QCommandLineParser>
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/PasswordGenerator.h"
const QCommandLineOption Generate::PasswordLengthOption =
QCommandLineOption(QStringList() << "L"
<< "length",
QObject::tr("Length of the generated password"),
QObject::tr("length"));
const QCommandLineOption Generate::LowerCaseOption = QCommandLineOption(QStringList() << "l"
<< "lower",
QObject::tr("Use lowercase characters"));
const QCommandLineOption Generate::UpperCaseOption = QCommandLineOption(QStringList() << "u"
<< "upper",
QObject::tr("Use uppercase characters"));
const QCommandLineOption Generate::NumbersOption = QCommandLineOption(QStringList() << "n"
<< "numeric",
QObject::tr("Use numbers"));
const QCommandLineOption Generate::SpecialCharsOption = QCommandLineOption(QStringList() << "s"
<< "special",
QObject::tr("Use special characters"));
const QCommandLineOption Generate::ExtendedAsciiOption = QCommandLineOption(QStringList() << "e"
<< "extended",
QObject::tr("Use extended ASCII"));
const QCommandLineOption Generate::ExcludeCharsOption = QCommandLineOption(QStringList() << "x"
<< "exclude",
QObject::tr("Exclude character set"),
QObject::tr("chars"));
const QCommandLineOption Generate::ExcludeSimilarCharsOption =
QCommandLineOption(QStringList() << "exclude-similar", QObject::tr("Exclude similar looking characters"));
const QCommandLineOption Generate::IncludeEveryGroupOption =
QCommandLineOption(QStringList() << "every-group", QObject::tr("Include characters from every selected group"));
Generate::Generate()
{
name = QString("generate");
description = QObject::tr("Generate a new random password.");
options.append(Generate::PasswordLengthOption);
options.append(Generate::LowerCaseOption);
options.append(Generate::UpperCaseOption);
options.append(Generate::NumbersOption);
options.append(Generate::SpecialCharsOption);
options.append(Generate::ExtendedAsciiOption);
options.append(Generate::ExcludeCharsOption);
options.append(Generate::ExcludeSimilarCharsOption);
options.append(Generate::IncludeEveryGroupOption);
}
Generate::~Generate()
@ -38,97 +81,59 @@ Generate::~Generate()
int Generate::execute(const QStringList& arguments)
{
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
QCommandLineOption len(QStringList() << "L"
<< "length",
QObject::tr("Length of the generated password"),
QObject::tr("length"));
parser.addOption(len);
QCommandLineOption lower(QStringList() << "l"
<< "lower",
QObject::tr("Use lowercase characters"));
parser.addOption(lower);
QCommandLineOption upper(QStringList() << "u"
<< "upper",
QObject::tr("Use uppercase characters"));
parser.addOption(upper);
QCommandLineOption numeric(QStringList() << "n"
<< "numeric",
QObject::tr("Use numbers."));
parser.addOption(numeric);
QCommandLineOption special(QStringList() << "s"
<< "special",
QObject::tr("Use special characters"));
parser.addOption(special);
QCommandLineOption extended(QStringList() << "e"
<< "extended",
QObject::tr("Use extended ASCII"));
parser.addOption(extended);
QCommandLineOption exclude(QStringList() << "x"
<< "exclude",
QObject::tr("Exclude character set"),
QObject::tr("chars"));
parser.addOption(exclude);
QCommandLineOption exclude_similar(QStringList() << "exclude-similar",
QObject::tr("Exclude similar looking characters"));
parser.addOption(exclude_similar);
QCommandLineOption every_group(QStringList() << "every-group",
QObject::tr("Include characters from every selected group"));
parser.addOption(every_group);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) {
errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE;
}
const QStringList args = parser->positionalArguments();
PasswordGenerator passwordGenerator;
if (parser.value(len).isEmpty()) {
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;
return EXIT_FAILURE;
} else {
passwordGenerator.setLength(parser.value(len).toInt());
passwordGenerator.setLength(passwordLength.toInt());
}
PasswordGenerator::CharClasses classes = 0x0;
if (parser.isSet(lower)) {
if (parser->isSet(Generate::LowerCaseOption)) {
classes |= PasswordGenerator::LowerLetters;
}
if (parser.isSet(upper)) {
if (parser->isSet(Generate::UpperCaseOption)) {
classes |= PasswordGenerator::UpperLetters;
}
if (parser.isSet(numeric)) {
if (parser->isSet(Generate::NumbersOption)) {
classes |= PasswordGenerator::Numbers;
}
if (parser.isSet(special)) {
if (parser->isSet(Generate::SpecialCharsOption)) {
classes |= PasswordGenerator::SpecialCharacters;
}
if (parser.isSet(extended)) {
if (parser->isSet(Generate::ExtendedAsciiOption)) {
classes |= PasswordGenerator::EASCII;
}
PasswordGenerator::GeneratorFlags flags = 0x0;
if (parser.isSet(exclude_similar)) {
if (parser->isSet(Generate::ExcludeSimilarCharsOption)) {
flags |= PasswordGenerator::ExcludeLookAlike;
}
if (parser.isSet(every_group)) {
if (parser->isSet(Generate::IncludeEveryGroupOption)) {
flags |= PasswordGenerator::CharFromEveryGroup;
}
passwordGenerator.setCharClasses(classes);
passwordGenerator.setFlags(flags);
passwordGenerator.setExcludedChars(parser.value(exclude));
passwordGenerator.setExcludedChars(parser->value(Generate::ExcludeCharsOption));
if (!passwordGenerator.isValid()) {
errorTextStream << parser.helpText().replace("[options]", "generate [options]");
errorTextStream << QObject::tr("invalid password generator after applying all options") << endl;
return EXIT_FAILURE;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -26,6 +26,16 @@ public:
Generate();
~Generate();
int execute(const QStringList& arguments) override;
static const QCommandLineOption PasswordLengthOption;
static const QCommandLineOption LowerCaseOption;
static const QCommandLineOption UpperCaseOption;
static const QCommandLineOption NumbersOption;
static const QCommandLineOption SpecialCharsOption;
static const QCommandLineOption ExtendedAsciiOption;
static const QCommandLineOption ExcludeCharsOption;
static const QCommandLineOption ExcludeSimilarCharsOption;
static const QCommandLineOption IncludeEveryGroupOption;
};
#endif // KEEPASSXC_GENERATE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,75 +21,44 @@
#include "List.h"
#include "cli/Utils.h"
#include <QCommandLineParser>
#include "cli/TextStream.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
const QCommandLineOption List::RecursiveOption =
QCommandLineOption(QStringList() << "R"
<< "recursive",
QObject::tr("Recursively list the elements of the group."));
List::List()
{
name = QString("ls");
description = QObject::tr("List database entries.");
options.append(List::RecursiveOption);
optionalArguments.append(
{QString("group"), QObject::tr("Path of the group to list. Default is /"), QString("[group]")});
}
List::~List()
{
}
int List::execute(const QStringList& arguments)
{
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), "[group]");
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption recursiveOption(QStringList() << "R"
<< "recursive",
QObject::tr("Recursively list the elements of the group."));
parser.addOption(recursiveOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 1 && args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "ls [options]");
return EXIT_FAILURE;
}
bool recursive = parser.isSet(recursiveOption);
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
if (args.size() == 2) {
return listGroup(db.data(), recursive, args.at(1));
}
return listGroup(db.data(), recursive);
}
int List::listGroup(Database* database, bool recursive, const QString& groupPath)
int List::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
if (groupPath.isEmpty()) {
const QStringList args = parser->positionalArguments();
bool recursive = parser->isSet(List::RecursiveOption);
// No group provided, defaulting to root group.
if (args.size() == 1) {
outputTextStream << database->rootGroup()->print(recursive) << flush;
return EXIT_SUCCESS;
}
QString groupPath = args.at(1);
Group* group = database->rootGroup()->findGroupByPath(groupPath);
if (!group) {
errorTextStream << QObject::tr("Cannot find group %1.").arg(groupPath) << endl;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,15 +18,17 @@
#ifndef KEEPASSXC_LIST_H
#define KEEPASSXC_LIST_H
#include "Command.h"
#include "DatabaseCommand.h"
class List : public Command
class List : public DatabaseCommand
{
public:
List();
~List();
int execute(const QStringList& arguments) override;
int listGroup(Database* database, bool recursive, const QString& groupPath = {});
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
static const QCommandLineOption RecursiveOption;
};
#endif // KEEPASSXC_LIST_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,7 +20,6 @@
#include "Locate.h"
#include <QCommandLineParser>
#include <QStringList>
#include "cli/TextStream.h"
@ -34,46 +33,18 @@ Locate::Locate()
{
name = QString("locate");
description = QObject::tr("Find entries quickly.");
positionalArguments.append({QString("term"), QObject::tr("Search term."), QString("")});
}
Locate::~Locate()
{
}
int Locate::execute(const QStringList& arguments)
int Locate::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addPositionalArgument("term", QObject::tr("Search term."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "locate [options]");
return EXIT_FAILURE;
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
return locateEntry(db.data(), args.at(1));
}
int Locate::locateEntry(Database* database, const QString& searchTerm)
{
const QStringList args = parser->positionalArguments();
QString searchTerm = args.at(1);
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,15 +18,15 @@
#ifndef KEEPASSXC_LOCATE_H
#define KEEPASSXC_LOCATE_H
#include "Command.h"
#include "DatabaseCommand.h"
class Locate : public Command
class Locate : public DatabaseCommand
{
public:
Locate();
~Locate();
int execute(const QStringList& arguments) override;
int locateEntry(Database* database, const QString& searchTerm);
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
};
#endif // KEEPASSXC_LOCATE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,79 +15,57 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Merge.h"
#include <cstdlib>
#include <QCommandLineParser>
#include "Merge.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "core/Merger.h"
#include <cstdlib>
const QCommandLineOption Merge::SameCredentialsOption =
QCommandLineOption(QStringList() << "s"
<< "same-credentials",
QObject::tr("Use the same credentials for both database files."));
const QCommandLineOption Merge::KeyFileFromOption =
QCommandLineOption(QStringList() << "k"
<< "key-file-from",
QObject::tr("Key file of the database to merge from."),
QObject::tr("path"));
const QCommandLineOption Merge::NoPasswordFromOption =
QCommandLineOption(QStringList() << "no-password-from",
QObject::tr("Deactivate password key for the database to merge from."));
Merge::Merge()
{
name = QString("merge");
description = QObject::tr("Merge two databases.");
options.append(Merge::SameCredentialsOption);
options.append(Merge::KeyFileFromOption);
options.append(Merge::NoPasswordFromOption);
positionalArguments.append({QString("database2"), QObject::tr("Path of the database to merge from."), QString("")});
}
Merge::~Merge()
{
}
int Merge::execute(const QStringList& arguments)
int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database1", QObject::tr("Path of the database to merge into."));
parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from."));
parser.addOption(Command::QuietOption);
QCommandLineOption samePasswordOption(QStringList() << "s"
<< "same-credentials",
QObject::tr("Use the same credentials for both database files."));
parser.addOption(samePasswordOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption keyFileFromOption(QStringList() << "f"
<< "key-file-from",
QObject::tr("Key file of the database to merge from."),
QObject::tr("path"));
parser.addOption(keyFileFromOption);
QCommandLineOption noPasswordFromOption(QStringList() << "no-password-from",
QObject::tr("Deactivate password key for the database to merge from."));
parser.addOption(noPasswordFromOption);
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "merge [options]");
return EXIT_FAILURE;
}
auto db1 = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db1) {
return EXIT_FAILURE;
}
const QStringList args = parser->positionalArguments();
QSharedPointer<Database> db2;
if (!parser.isSet("same-credentials")) {
if (!parser->isSet(Merge::SameCredentialsOption)) {
db2 = Utils::unlockDatabase(args.at(1),
!parser.isSet(noPasswordFromOption),
parser.value(keyFileFromOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
!parser->isSet(Merge::NoPasswordFromOption),
parser->value(Merge::KeyFileFromOption),
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db2) {
return EXIT_FAILURE;
@ -95,25 +73,25 @@ int Merge::execute(const QStringList& arguments)
} else {
db2 = QSharedPointer<Database>::create();
QString errorMessage;
if (!db2->open(args.at(1), db1->key(), &errorMessage, false)) {
if (!db2->open(args.at(1), database->key(), &errorMessage, false)) {
errorTextStream << QObject::tr("Error reading merge file:\n%1").arg(errorMessage);
return EXIT_FAILURE;
}
}
Merger merger(db2.data(), db1.data());
Merger merger(db2.data(), database.data());
bool databaseChanged = merger.merge();
if (databaseChanged) {
QString errorMessage;
if (!db1->save(args.at(0), &errorMessage, true, false)) {
if (!database->save(args.at(0), &errorMessage, true, false)) {
errorTextStream << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (!parser.isSet(Command::QuietOption)) {
if (!parser->isSet(Command::QuietOption)) {
outputTextStream << "Successfully merged the database files." << endl;
}
} else if (!parser.isSet(Command::QuietOption)) {
} else if (!parser->isSet(Command::QuietOption)) {
outputTextStream << "Database was not modified by merge operation." << endl;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,14 +18,19 @@
#ifndef KEEPASSXC_MERGE_H
#define KEEPASSXC_MERGE_H
#include "Command.h"
#include "DatabaseCommand.h"
class Merge : public Command
class Merge : public DatabaseCommand
{
public:
Merge();
~Merge();
int execute(const QStringList& arguments) override;
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
static const QCommandLineOption SameCredentialsOption;
static const QCommandLineOption KeyFileFromOption;
static const QCommandLineOption NoPasswordFromOption;
};
#endif // KEEPASSXC_MERGE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,10 +20,6 @@
#include "Remove.h"
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QStringList>
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
@ -36,46 +32,19 @@ Remove::Remove()
{
name = QString("rm");
description = QString("Remove an entry from the database.");
positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to remove."), QString("")});
}
Remove::~Remove()
{
}
int Remove::execute(const QStringList& arguments)
int Remove::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
bool quiet = parser->isSet(Command::QuietOption);
QString databasePath = parser->positionalArguments().at(0);
QString entryPath = parser->positionalArguments().at(1);
QCommandLineParser parser;
parser.setApplicationDescription(QObject::tr("Remove an entry from the database."));
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to remove."));
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "rm [options]");
return EXIT_FAILURE;
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
return removeEntry(db.data(), args.at(0), args.at(1), parser.isSet(Command::QuietOption));
}
int Remove::removeEntry(Database* database, const QString& databasePath, const QString& entryPath, bool quiet)
{
TextStream outputTextStream(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);

View File

@ -18,17 +18,15 @@
#ifndef KEEPASSXC_REMOVE_H
#define KEEPASSXC_REMOVE_H
#include "Command.h"
#include "DatabaseCommand.h"
#include "core/Database.h"
class Remove : public Command
class Remove : public DatabaseCommand
{
public:
Remove();
~Remove();
int execute(const QStringList& arguments) override;
int removeEntry(Database* database, const QString& databasePath, const QString& entryPath, bool quiet);
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
};
#endif // KEEPASSXC_REMOVE_H

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,8 +20,6 @@
#include <cstdlib>
#include <stdio.h>
#include <QCommandLineParser>
#include "Utils.h"
#include "cli/TextStream.h"
#include "core/Database.h"
@ -29,67 +27,42 @@
#include "core/Global.h"
#include "core/Group.h"
const QCommandLineOption Show::TotpOption = QCommandLineOption(QStringList() << "t"
<< "totp",
QObject::tr("Show the entry's current TOTP."));
const QCommandLineOption Show::AttributesOption = QCommandLineOption(
QStringList() << "a"
<< "attributes",
QObject::tr(
"Names of the attributes to show. "
"This option can be specified more than once, with each attribute shown one-per-line in the given order. "
"If no attributes are specified, a summary of the default attributes is given."),
QObject::tr("attribute"));
Show::Show()
{
name = QString("show");
description = QObject::tr("Show an entry's information.");
options.append(Show::TotpOption);
options.append(Show::AttributesOption);
positionalArguments.append({QString("entry"), QObject::tr("Name of the entry to show."), QString("")});
}
Show::~Show()
{
}
int Show::execute(const QStringList& arguments)
{
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
QCommandLineParser parser;
parser.setApplicationDescription(description);
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption totp(QStringList() << "t"
<< "totp",
QObject::tr("Show the entry's current TOTP."));
parser.addOption(totp);
QCommandLineOption attributes(
QStringList() << "a"
<< "attributes",
QObject::tr(
"Names of the attributes to show. "
"This option can be specified more than once, with each attribute shown one-per-line in the given order. "
"If no attributes are specified, a summary of the default attributes is given."),
QObject::tr("attribute"));
parser.addOption(attributes);
parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show."));
parser.addHelpOption();
parser.process(arguments);
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
errorTextStream << parser.helpText().replace("[options]", "show [options]");
return EXIT_FAILURE;
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
return EXIT_FAILURE;
}
return showEntry(db.data(), parser.values(attributes), parser.isSet(totp), args.at(1));
}
int Show::showEntry(Database* database, QStringList attributes, bool showTotp, const QString& entryPath)
int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1);
bool showTotp = parser->isSet(Show::TotpOption);
QStringList attributes = parser->values(Show::AttributesOption);
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) {
errorTextStream << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,15 +18,18 @@
#ifndef KEEPASSXC_SHOW_H
#define KEEPASSXC_SHOW_H
#include "Command.h"
#include "DatabaseCommand.h"
class Show : public Command
class Show : public DatabaseCommand
{
public:
Show();
~Show();
int execute(const QStringList& arguments) override;
int showEntry(Database* database, QStringList attributes, bool showTotp, const QString& entryPath);
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser);
static const QCommandLineOption TotpOption;
static const QCommandLineOption AttributesOption;
};
#endif // KEEPASSXC_SHOW_H

View File

@ -197,6 +197,7 @@ void TestCli::testAdd()
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"));
auto db = readTestDatabase();
@ -296,7 +297,9 @@ void TestCli::testClip()
// Password with timeout
Utils::Test::setNextPassword("a");
// clang-format off
QFuture<void> future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"});
QFuture<void> future = QtConcurrent::run(&clipCmd,
static_cast<int(Clip::*)(const QStringList&)>(&DatabaseCommand::execute),
QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"});
// clang-format on
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500);
@ -306,8 +309,9 @@ void TestCli::testClip()
// TOTP with timeout
Utils::Test::setNextPassword("a");
future = QtConcurrent::run(
&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1", "-t"});
future = QtConcurrent::run(&clipCmd,
static_cast<int (Clip::*)(const QStringList&)>(&DatabaseCommand::execute),
QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1", "-t"});
QTRY_VERIFY_WITH_TIMEOUT(isTOTP(clipboard->text()), 500);
QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500);
@ -316,6 +320,18 @@ void TestCli::testClip()
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"));
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"));
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"));
@ -414,6 +430,18 @@ void TestCli::testDiceware()
passphrase = m_stdoutFile->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"));
// 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"));
TemporaryFile wordFile;
wordFile.open();
for (int i = 0; i < 4500; ++i) {
@ -431,6 +459,18 @@ void TestCli::testDiceware()
for (const auto& word : words) {
QVERIFY2(regex.match(word).hasMatch(), qPrintable("Word " + word + " was not on the word list"));
}
TemporaryFile smallWordFile;
smallWordFile.open();
for (int i = 0; i < 50; ++i) {
smallWordFile.write(QString("word" + QString::number(i) + "\n").toLatin1());
}
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"));
}
void TestCli::testEdit()
@ -679,6 +719,23 @@ void TestCli::testGenerate()
QVERIFY2(regex.match(password).hasMatch(),
qPrintable("Password " + password + " does not match pattern " + pattern));
}
// 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"));
posErr = m_stderrFile->pos();
generateCmd.execute({"generate", "-L", "0"});
m_stderrFile->seek(posErr);
QCOMPARE(m_stderrFile->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"));
}
void TestCli::testKeyFileOption()