Skip to content

Commit

Permalink
Add interactive session mode to keepassxc-cli.
Browse files Browse the repository at this point in the history
This change adds a GNU Readline-based interactive mode to keepassxc-cli.
If GNU Readline is not available, commands are just read from stdin
with no editing support.

DatabaseCommand is modified to add the path to the current database to
the arguments passed to executeWithDatabase. In this way, instances of
DatabaseCommand do not have to prompt to re-open the database after each
invocation, and existing command implementations do not have to be
changed to support interactive mode.

Fixes keepassxreboot#3224.
  • Loading branch information
sjamesr committed Aug 6, 2019
1 parent 7cbcea1 commit b2c2951
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 14 deletions.
50 changes: 50 additions & 0 deletions cmake/FindReadline.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Code copied from sethhall@github
#
# - Try to find readline include dirs and libraries
#
# Usage of this module as follows:
#
# find_package(Readline)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# Readline_ROOT_DIR Set this variable to the root installation of
# readline if the module has problems finding the
# proper installation path.
#
# Variables defined by this module:
#
# READLINE_FOUND System has readline, include and lib dirs found
# Readline_INCLUDE_DIR The readline include directories.
# Readline_LIBRARY The readline library.

find_path(Readline_ROOT_DIR
NAMES include/readline/readline.h
)

find_path(Readline_INCLUDE_DIR
NAMES readline/readline.h
HINTS ${Readline_ROOT_DIR}/include
)

find_library(Readline_LIBRARY
NAMES readline
HINTS ${Readline_ROOT_DIR}/lib
)

if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
set(READLINE_FOUND TRUE)
else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
FIND_LIBRARY(Readline_LIBRARY NAMES readline)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY )
MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY)
endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)

mark_as_advanced(
Readline_ROOT_DIR
Readline_INCLUDE_DIR
Readline_LIBRARY
)

9 changes: 9 additions & 0 deletions src/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ set(cli_SOURCES
Clip.cpp
Create.cpp
Command.cpp
CommandParser.cpp
DatabaseCommand.cpp
Diceware.cpp
Edit.cpp
Expand All @@ -28,12 +29,20 @@ set(cli_SOURCES
List.cpp
Locate.cpp
Merge.cpp
Open.cpp
Remove.cpp
Show.cpp)

add_library(cli STATIC ${cli_SOURCES})
target_link_libraries(cli Qt5::Core Qt5::Widgets)

find_package(Readline)

if (READLINE_FOUND)
target_compile_definitions(cli PUBLIC USE_READLINE)
target_link_libraries(cli readline)
endif()

add_executable(keepassxc-cli keepassxc-cli.cpp)
target_link_libraries(keepassxc-cli
cli
Expand Down
6 changes: 4 additions & 2 deletions src/cli/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "List.h"
#include "Locate.h"
#include "Merge.h"
#include "Open.h"
#include "Remove.h"
#include "Show.h"
#include "TextStream.h"
Expand Down Expand Up @@ -94,11 +95,11 @@ QSharedPointer<QCommandLineParser> Command::getCommandLineParser(const QStringLi
parser->process(arguments);

if (parser->positionalArguments().size() < positionalArguments.size()) {
errorTextStream << parser->helpText().replace("[options]", name.append(" [options]"));
errorTextStream << parser->helpText().replace("[options]", name + " [options]");
return QSharedPointer<QCommandLineParser>(nullptr);
}
if (parser->positionalArguments().size() > (positionalArguments.size() + optionalArguments.size())) {
errorTextStream << parser->helpText().replace("[options]", name.append(" [options]"));
errorTextStream << parser->helpText().replace("[options]", name + " [options]");
return QSharedPointer<QCommandLineParser>(nullptr);
}
return parser;
Expand All @@ -119,6 +120,7 @@ void populateCommands()
commands.insert(QString("locate"), new Locate());
commands.insert(QString("ls"), new List());
commands.insert(QString("merge"), new Merge());
commands.insert(QString("open"), new Open());
commands.insert(QString("rm"), new Remove());
commands.insert(QString("show"), new Show());
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Command
virtual int execute(const QStringList& arguments) = 0;
QString name;
QString description;
QSharedPointer<Database> currentDatabase;
QList<CommandLineArgument> positionalArguments;
QList<CommandLineArgument> optionalArguments;
QList<QCommandLineOption> options;
Expand Down
48 changes: 48 additions & 0 deletions src/cli/CommandParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 "CommandParser.h"

QStringList const CommandParser::parse(const QString& command)
{
QStringList result;

bool inside_quotes = false;
QString cur;
for (int i = 0; i < command.size(); ++i) {
QChar c = command[i];
if (c == '\\' && i < command.size() - 1) {
cur.append(command[i + 1]);
++i;
} else if (!inside_quotes && (c == ' ' || c == '\t')) {
if (!cur.isEmpty()) {
result.append(cur);
cur.clear();
}
} else if (c == '"' && (inside_quotes || i == 0 || command[i - 1].isSpace())) {
inside_quotes = !inside_quotes;
} else {
cur.append(c);
}
}

if (!cur.isEmpty()) {
result.append(cur);
}

return result;
}
38 changes: 38 additions & 0 deletions src/cli/CommandParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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_COMMANDPARSER_H
#define KEEPASSXC_COMMANDPARSER_H

#include <QtCore/QStringList>

// Implements basic string splitting for the readline-based interactive CLI.
class CommandParser
{
public:
CommandParser() = default;

// Splits the given QString into a QString list. For example:
//
// "hello world" -> ["hello", "world"]
// "hello world" -> ["hello", "world"]
// "hello\\ world" -> ["hello world"] (i.e. backslash is an escape character
// "\"hello world\"" -> ["hello world"]
QStringList const parse(const QString& command);
};

#endif // KEEPASSXC_COMMANDPARSER_H
28 changes: 18 additions & 10 deletions src/cli/DatabaseCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,28 @@ DatabaseCommand::DatabaseCommand()

int DatabaseCommand::execute(const QStringList& arguments)
{
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
QStringList amendedArgs(arguments);
if (currentDatabase) {
amendedArgs.prepend(currentDatabase->filePath());
}
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(amendedArgs);

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;
QStringList args = parser->positionalArguments();
if (!currentDatabase) {
currentDatabase = Utils::unlockDatabase(args.at(0),
!parser->isSet(Command::NoPasswordOption),
parser->value(Command::KeyFileOption),
parser->isSet(Command::QuietOption)
? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!currentDatabase) {
return EXIT_FAILURE;
}
}

return executeWithDatabase(db, parser);
return executeWithDatabase(currentDatabase, parser);
}
37 changes: 37 additions & 0 deletions src/cli/Open.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 "Open.h"

#include <QtCore/QCommandLineParser>

#include "DatabaseCommand.h"
#include "TextStream.h"
#include "Utils.h"

Open::Open()
{
name = QString("open");
description = QObject::tr("Open a database.");
}

int Open::executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser)
{
Q_UNUSED(parser)
currentDatabase = db;
return EXIT_SUCCESS;
}
31 changes: 31 additions & 0 deletions src/cli/Open.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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_OPEN_H
#define KEEPASSXC_OPEN_H

#include "DatabaseCommand.h"

class Open : public DatabaseCommand
{
public:
Open();
~Open() override = default;
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
};

#endif // KEEPASSXC_OPEN_H
Loading

0 comments on commit b2c2951

Please sign in to comment.