diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 975d549e5..6cbe2b1a1 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -20,8 +20,6 @@ #include "Add.h" -#include - #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, QSharedPointer 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; diff --git a/src/cli/Add.h b/src/cli/Add.h index dd0c3d8b5..7c8d40e32 100644 --- a/src/cli/Add.h +++ b/src/cli/Add.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer 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 diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 2f4a7275e..6473eb559 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -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 diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index e1e74c682..72e6bfdca 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -22,80 +22,52 @@ #include "Clip.h" -#include - #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, QSharedPointer 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, - 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; diff --git a/src/cli/Clip.h b/src/cli/Clip.h index 65a616955..692bb122f 100644 --- a/src/cli/Clip.h +++ b/src/cli/Clip.h @@ -1,5 +1,5 @@ /* - * 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,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, - const QString& entryPath, - const QString& timeout, - bool clipTotp, - bool silent); + + int executeWithDatabase(QSharedPointer db, QSharedPointer parser); + + static const QCommandLineOption TotpOption; }; #endif // KEEPASSXC_CLIP_H diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index 48e1f4116..723821533 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -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 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 Command::getCommandLineParser(const QStringList& arguments) +{ + TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); + + QSharedPointer parser = QSharedPointer(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(nullptr); + } + if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) { + errorTextStream << parser->helpText().replace("[options]", name.append(" [options]")); + return QSharedPointer(nullptr); + } + return parser; +} + void populateCommands() { if (commands.isEmpty()) { diff --git a/src/cli/Command.h b/src/cli/Command.h index 30af61702..7f3494ba6 100644 --- a/src/cli/Command.h +++ b/src/cli/Command.h @@ -1,5 +1,5 @@ /* - * 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 @@ -19,6 +19,7 @@ #define KEEPASSXC_COMMAND_H #include +#include #include #include #include @@ -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 positionalArguments; + QList optionalArguments; + QList options; QString getDescriptionLine(); + QSharedPointer getCommandLineParser(const QStringList& arguments); static QList getCommands(); static Command* getCommand(const QString& commandName); diff --git a/src/cli/Create.cpp b/src/cli/Create.cpp index 80dcb5691..6e0296d39 100644 --- a/src/cli/Create.cpp +++ b/src/cli/Create.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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,7 +18,6 @@ #include #include -#include #include #include #include @@ -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 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; - 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; } diff --git a/src/cli/DatabaseCommand.cpp b/src/cli/DatabaseCommand.cpp new file mode 100644 index 000000000..65d4f15b2 --- /dev/null +++ b/src/cli/DatabaseCommand.cpp @@ -0,0 +1,47 @@ +/* + * 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 + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "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 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); +} diff --git a/src/cli/DatabaseCommand.h b/src/cli/DatabaseCommand.h new file mode 100644 index 000000000..61847ffa2 --- /dev/null +++ b/src/cli/DatabaseCommand.h @@ -0,0 +1,35 @@ +/* + * 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 + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_DATABASECOMMAND_H +#define KEEPASSXC_DATABASECOMMAND_H + +#include + +#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 db, QSharedPointer parser) = 0; +}; + +#endif // KEEPASSXC_DATABASECOMMAND_H diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index e14487659..25e03bdd0 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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 @@ -20,16 +20,28 @@ #include "Diceware.h" -#include - #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 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; } diff --git a/src/cli/Diceware.h b/src/cli/Diceware.h index f439681b7..8b2d6835d 100644 --- a/src/cli/Diceware.h +++ b/src/cli/Diceware.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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 @@ -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 diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 59cedd7c9..6f65228e0 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -20,8 +20,7 @@ #include "Edit.h" -#include - +#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, QSharedPointer 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; } diff --git a/src/cli/Edit.h b/src/cli/Edit.h index 001b5abaf..46c1250c5 100644 --- a/src/cli/Edit.h +++ b/src/cli/Edit.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer parser); + + static const QCommandLineOption TitleOption; }; #endif // KEEPASSXC_EDIT_H diff --git a/src/cli/Estimate.cpp b/src/cli/Estimate.cpp index c278b50f3..4ea754f93 100644 --- a/src/cli/Estimate.cpp +++ b/src/cli/Estimate.cpp @@ -1,5 +1,5 @@ /* - * 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,8 +18,6 @@ #include "Estimate.h" #include "cli/Utils.h" -#include - #include "cli/TextStream.h" #include #include @@ -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 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; } diff --git a/src/cli/Estimate.h b/src/cli/Estimate.h index c15fed9b3..27aec6036 100644 --- a/src/cli/Estimate.h +++ b/src/cli/Estimate.h @@ -1,5 +1,5 @@ /* - * 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 @@ -26,6 +26,8 @@ public: Estimate(); ~Estimate(); int execute(const QStringList& arguments) override; + + static const QCommandLineOption AdvancedOption; }; #endif // KEEPASSXC_ESTIMATE_H diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 90713e1ec..b894277fe 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -20,9 +20,6 @@ #include "Extract.h" -#include -#include - #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, QSharedPointer) { 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::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; } diff --git a/src/cli/Extract.h b/src/cli/Extract.h index 1a4f6288b..7a6061b98 100644 --- a/src/cli/Extract.h +++ b/src/cli/Extract.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer parser); }; #endif // KEEPASSXC_EXTRACT_H diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index e8ca90275..8972942b8 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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 @@ -19,17 +19,60 @@ #include #include "Generate.h" -#include "cli/Utils.h" - -#include #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 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; } diff --git a/src/cli/Generate.h b/src/cli/Generate.h index 64ef81623..f5e1638af 100644 --- a/src/cli/Generate.h +++ b/src/cli/Generate.h @@ -1,5 +1,5 @@ /* - * 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 @@ -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 diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 52797470c..37eb6f847 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -21,75 +21,44 @@ #include "List.h" #include "cli/Utils.h" -#include - #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, QSharedPointer 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; diff --git a/src/cli/List.h b/src/cli/List.h index 92ade262b..51026271b 100644 --- a/src/cli/List.h +++ b/src/cli/List.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer parser); + + static const QCommandLineOption RecursiveOption; }; #endif // KEEPASSXC_LIST_H diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index af5f24196..4edfa7519 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -20,7 +20,6 @@ #include "Locate.h" -#include #include #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, QSharedPointer 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); diff --git a/src/cli/Locate.h b/src/cli/Locate.h index ae32951b9..24a47731a 100644 --- a/src/cli/Locate.h +++ b/src/cli/Locate.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer parser); }; #endif // KEEPASSXC_LOCATE_H diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 2356f5d3a..8e4cf8ad0 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -15,79 +15,57 @@ * along with this program. If not, see . */ -#include "Merge.h" +#include -#include +#include "Merge.h" #include "cli/TextStream.h" #include "cli/Utils.h" #include "core/Database.h" #include "core/Merger.h" -#include +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, QSharedPointer 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 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::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; } diff --git a/src/cli/Merge.h b/src/cli/Merge.h index 1f138b65f..108f654c3 100644 --- a/src/cli/Merge.h +++ b/src/cli/Merge.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer parser); + + static const QCommandLineOption SameCredentialsOption; + static const QCommandLineOption KeyFileFromOption; + static const QCommandLineOption NoPasswordFromOption; }; #endif // KEEPASSXC_MERGE_H diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index bb2374e9a..0baa16694 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -20,10 +20,6 @@ #include "Remove.h" -#include -#include -#include - #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, QSharedPointer 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); diff --git a/src/cli/Remove.h b/src/cli/Remove.h index fa8bc7bd6..07697f8a3 100644 --- a/src/cli/Remove.h +++ b/src/cli/Remove.h @@ -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 db, QSharedPointer parser); }; #endif // KEEPASSXC_REMOVE_H diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 3abccd79c..f33ee7d98 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -20,8 +20,6 @@ #include #include -#include - #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, QSharedPointer 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; diff --git a/src/cli/Show.h b/src/cli/Show.h index 32dab7efb..dc496c194 100644 --- a/src/cli/Show.h +++ b/src/cli/Show.h @@ -1,5 +1,5 @@ /* - * 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,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 db, QSharedPointer parser); + + static const QCommandLineOption TotpOption; + static const QCommandLineOption AttributesOption; }; #endif // KEEPASSXC_SHOW_H diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 23af631e6..b1da5f1a1 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -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 future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"}); + QFuture future = QtConcurrent::run(&clipCmd, + static_cast(&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(&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()