2015-09-06 20:33:09 +02:00
|
|
|
#include "./cli.h"
|
2015-04-22 19:30:09 +02:00
|
|
|
|
|
|
|
#include <passwordfile/io/cryptoexception.h>
|
|
|
|
#include <passwordfile/io/entry.h>
|
|
|
|
#include <passwordfile/io/field.h>
|
2017-05-01 03:26:04 +02:00
|
|
|
#include <passwordfile/io/parsingexception.h>
|
|
|
|
#include <passwordfile/io/passwordfile.h>
|
2015-04-22 19:30:09 +02:00
|
|
|
|
2015-09-01 20:18:13 +02:00
|
|
|
#include <c++utilities/application/commandlineutils.h>
|
2017-05-01 03:26:04 +02:00
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
2017-12-17 23:38:12 +01:00
|
|
|
#include <c++utilities/io/ansiescapecodes.h>
|
2016-06-14 22:57:39 +02:00
|
|
|
#include <c++utilities/io/catchiofailure.h>
|
2015-04-22 19:30:09 +02:00
|
|
|
|
|
|
|
#if defined(PLATFORM_UNIX)
|
2017-05-01 03:26:04 +02:00
|
|
|
#include <unistd.h>
|
2015-04-22 19:30:09 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <functional>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace std::placeholders;
|
2017-12-17 23:38:45 +01:00
|
|
|
using namespace ApplicationUtilities;
|
2015-04-22 19:30:09 +02:00
|
|
|
using namespace ConversionUtilities;
|
2017-12-17 23:38:12 +01:00
|
|
|
using namespace EscapeCodes;
|
2016-06-14 22:57:39 +02:00
|
|
|
using namespace IoUtilities;
|
2015-04-22 19:30:09 +02:00
|
|
|
using namespace Io;
|
|
|
|
|
|
|
|
namespace Cli {
|
|
|
|
|
|
|
|
InputMuter::InputMuter()
|
|
|
|
{
|
|
|
|
#if defined(PLATFORM_UNIX)
|
|
|
|
tcgetattr(STDIN_FILENO, &m_attr);
|
|
|
|
termios newAttr = m_attr;
|
2017-06-10 22:26:12 +02:00
|
|
|
newAttr.c_lflag &= ~static_cast<unsigned int>(ECHO);
|
2015-04-22 19:30:09 +02:00
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &newAttr);
|
|
|
|
#elif defined(PLATFORM_WINDOWS)
|
|
|
|
m_cinHandle = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
m_mode = 0;
|
|
|
|
GetConsoleMode(m_cinHandle, &m_mode);
|
|
|
|
SetConsoleMode(m_cinHandle, m_mode & (~ENABLE_ECHO_INPUT));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
InputMuter::~InputMuter()
|
|
|
|
{
|
|
|
|
#if defined(PLATFORM_UNIX)
|
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &m_attr);
|
|
|
|
#elif defined(PLATFORM_WINDOWS)
|
|
|
|
SetConsoleMode(m_cinHandle, m_mode);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearConsole()
|
|
|
|
{
|
|
|
|
#if defined(PLATFORM_WINDOWS)
|
2017-05-01 03:26:04 +02:00
|
|
|
HANDLE hStdOut;
|
2015-04-22 19:30:09 +02:00
|
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
2017-05-01 03:26:04 +02:00
|
|
|
DWORD count;
|
|
|
|
DWORD cellCount;
|
|
|
|
COORD homeCoords = { 0, 0 };
|
2015-04-22 19:30:09 +02:00
|
|
|
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
2017-05-01 03:26:04 +02:00
|
|
|
if (hStdOut == INVALID_HANDLE_VALUE) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// get the number of cells in the current buffer
|
2017-05-01 03:26:04 +02:00
|
|
|
if (!GetConsoleScreenBufferInfo(hStdOut, &csbi)) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
cellCount = csbi.dwSize.X * csbi.dwSize.Y;
|
|
|
|
// fill the entire buffer with spaces
|
2017-05-01 03:26:04 +02:00
|
|
|
if (!FillConsoleOutputCharacter(hStdOut, (TCHAR)' ', cellCount, homeCoords, &count)) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// fill the entire buffer with the current colors and attributes
|
2017-05-01 03:26:04 +02:00
|
|
|
if (!FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, cellCount, homeCoords, &count)) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// move the cursor home
|
|
|
|
SetConsoleCursorPosition(hStdOut, homeCoords);
|
|
|
|
#else
|
|
|
|
EscapeCodes::setCursor(cout);
|
|
|
|
EscapeCodes::eraseDisplay(cout);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-05-01 03:26:04 +02:00
|
|
|
InteractiveCli::InteractiveCli()
|
|
|
|
: m_o(cout)
|
|
|
|
, m_i(cin)
|
|
|
|
, m_currentEntry(nullptr)
|
|
|
|
, m_modified(false)
|
|
|
|
, m_quit(false)
|
2015-09-01 20:18:13 +02:00
|
|
|
{
|
|
|
|
CMD_UTILS_START_CONSOLE;
|
|
|
|
}
|
2015-04-22 19:30:09 +02:00
|
|
|
|
|
|
|
void InteractiveCli::run(const string &file)
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
if (!file.empty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
openFile(file, false);
|
|
|
|
}
|
|
|
|
string input;
|
2017-05-01 03:26:04 +02:00
|
|
|
while (!m_quit) {
|
2015-04-22 19:30:09 +02:00
|
|
|
getline(m_i, input);
|
2017-05-01 03:26:04 +02:00
|
|
|
if (!input.empty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
processCommand(input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::processCommand(const string &cmd)
|
|
|
|
{
|
|
|
|
#define CMD(value) !paramMissing && cmd == value
|
|
|
|
#define CMD2(value1, value2) !paramMissing && (cmd == value1 || cmd == value2)
|
|
|
|
#define CMD_P(value) !paramMissing && checkCommand(cmd, value, param, paramMissing)
|
|
|
|
#define CMD2_P(value1, value2) !paramMissing && (checkCommand(cmd, value1, param, paramMissing) || checkCommand(cmd, value2, param, paramMissing))
|
|
|
|
|
|
|
|
string param;
|
|
|
|
bool paramMissing = false;
|
2017-05-01 03:26:04 +02:00
|
|
|
if (CMD2("quit", "q")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
quit();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD("q!")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_quit = true;
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD("wq")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
saveFile();
|
|
|
|
m_quit = true;
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2("clear", "c")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
clearConsole();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("openreadonly", "or")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
openFile(param, true);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("open", "o")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
openFile(param, false);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2("close", "c")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
closeFile();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2("save", "w")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
saveFile();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("create", "cr")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
createFile(param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD("chpassphrase")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
changePassphrase();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD("rmpassphrase")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
removePassphrase();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD("pwd")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
pwd();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD_P("cd")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
cd(param);
|
2017-06-10 22:26:12 +02:00
|
|
|
} else if (CMD2("ls", "l")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
ls();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2("tree", "t")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
tree();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("mknode", "mkn")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
makeEntry(EntryType::Node, param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("mkaccount", "mka")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
makeEntry(EntryType::Account, param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2("rmentry", "rme")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
removeEntry(".");
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("rmentry", "rme")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
removeEntry(param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("rnentry", "rne")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
renameEntry(param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("mventry", "me")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
moveEntry(param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("readfield", "rf")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
readField(param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("setfield", "sf")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
setField(false, param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("setfieldpw", "sp")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
setField(true, param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2_P("rmfield", "rf")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
removeField(param);
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (CMD2("help", "?")) {
|
2015-04-22 19:30:09 +02:00
|
|
|
printHelp();
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (paramMissing) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "parameter is missing" << endl;
|
|
|
|
} else {
|
|
|
|
m_o << "command is unknown" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Entry *InteractiveCli::resolvePath(const string &path)
|
|
|
|
{
|
2017-06-09 00:42:32 +02:00
|
|
|
auto parts = splitString<vector<string>>(path, "/", EmptyPartsTreat::Merge);
|
2015-04-22 19:30:09 +02:00
|
|
|
bool fromRoot = path.at(0) == '/';
|
2017-05-01 03:26:04 +02:00
|
|
|
if (fromRoot && parts.empty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return m_file.rootEntry();
|
|
|
|
} else {
|
|
|
|
Entry *entry = fromRoot ? m_file.rootEntry() : m_currentEntry;
|
2017-05-01 03:26:04 +02:00
|
|
|
for (const string &part : parts) {
|
|
|
|
if (part == "..") {
|
|
|
|
if (entry->parent()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
entry = entry->parent();
|
|
|
|
} else {
|
|
|
|
m_o << "can not resolve path; entry \"" << entry->label() << "\" is root" << endl;
|
|
|
|
return nullptr;
|
|
|
|
}
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (part != ".") {
|
|
|
|
switch (entry->type()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
case EntryType::Account:
|
|
|
|
m_o << "can not resolve path; entry \"" << entry->label() << "\" is not a node entry" << endl;
|
|
|
|
return nullptr;
|
|
|
|
case EntryType::Node:
|
2017-05-01 03:26:04 +02:00
|
|
|
for (Entry *child : (static_cast<NodeEntry *>(entry)->children())) {
|
|
|
|
if (child->label() == part) {
|
2015-04-22 19:30:09 +02:00
|
|
|
entry = child;
|
|
|
|
goto next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_o << "can not resolve path; entry \"" << entry->label() << "\" has no child \"" << part << "\"" << endl;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 03:26:04 +02:00
|
|
|
next:;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InteractiveCli::checkCommand(const string &str, const char *phrase, std::string ¶m, bool ¶mMissing)
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
for (auto i = str.cbegin(), end = str.cend(); i != end; ++i, ++phrase) {
|
|
|
|
if (*phrase == 0) {
|
|
|
|
if (*i == ' ') {
|
|
|
|
if (++i != end) {
|
2015-04-22 19:30:09 +02:00
|
|
|
param.assign(i, end);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
paramMissing = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2017-05-01 03:26:04 +02:00
|
|
|
} else if (*i != *phrase) {
|
2015-04-22 19:30:09 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
paramMissing = *phrase == 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::openFile(const string &file, bool readOnly)
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
if (m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "file \"" << m_file.path() << "\" currently open; close first" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_file.setPath(file);
|
2017-12-17 23:38:45 +01:00
|
|
|
for (;;) {
|
2015-04-22 19:30:09 +02:00
|
|
|
try {
|
2017-12-17 23:38:45 +01:00
|
|
|
try {
|
|
|
|
m_file.open(readOnly);
|
|
|
|
if (m_file.isEncryptionUsed()) {
|
|
|
|
m_file.setPassword(askForPassphrase());
|
|
|
|
}
|
|
|
|
m_file.load();
|
|
|
|
m_currentEntry = m_file.rootEntry();
|
|
|
|
m_o << "file \"" << file << "\" opened" << endl;
|
|
|
|
} catch (const ParsingException &) {
|
|
|
|
m_o << "error occured when parsing file \"" << file << "\"" << endl;
|
|
|
|
throw;
|
|
|
|
} catch (const CryptoException &) {
|
|
|
|
m_o << "error occured when decrypting file \"" << file << "\"" << endl;
|
|
|
|
throw;
|
|
|
|
} catch (...) {
|
|
|
|
const char *what = catchIoFailure();
|
|
|
|
m_o << "IO error occured when opening file \"" << file << "\"" << endl;
|
|
|
|
throw ios_base::failure(what);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-12-17 23:38:45 +01:00
|
|
|
} catch (const std::exception &e) {
|
|
|
|
if (*e.what() != 0) {
|
|
|
|
m_o << e.what() << endl;
|
|
|
|
}
|
|
|
|
if (confirmPrompt("Retry opening?", Response::Yes)) {
|
|
|
|
m_file.close();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
m_file.clear();
|
|
|
|
m_currentEntry = nullptr;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-12-17 23:38:45 +01:00
|
|
|
m_modified = false;
|
|
|
|
return;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::closeFile()
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "no file was opened" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
m_file.clear();
|
|
|
|
m_currentEntry = nullptr;
|
|
|
|
m_o << "file closed" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::saveFile()
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
|
|
|
m_o << "nothing to save; no file opened or created" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
2015-04-22 19:30:09 +02:00
|
|
|
try {
|
2017-06-10 22:26:12 +02:00
|
|
|
m_file.save(*m_file.password());
|
|
|
|
m_o << "file \"" << m_file.path() << "\" saved" << endl;
|
|
|
|
} catch (const ParsingException &) {
|
|
|
|
m_o << "error occured when parsing file \"" << m_file.path() << "\"" << endl;
|
|
|
|
throw;
|
|
|
|
} catch (const CryptoException &) {
|
|
|
|
m_o << "error occured when encrypting file \"" << m_file.path() << "\"" << endl;
|
|
|
|
throw;
|
|
|
|
} catch (...) {
|
|
|
|
const char *what = catchIoFailure();
|
|
|
|
m_o << "IO error occured when saving file \"" << m_file.path() << "\"" << endl;
|
|
|
|
throw ios_base::failure(what);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
} catch (const exception &e) {
|
|
|
|
if (*e.what() != 0) {
|
|
|
|
m_o << e.what() << endl;
|
|
|
|
}
|
|
|
|
m_o << "file has been closed; try reopening the file" << endl;
|
|
|
|
m_file.clear();
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
m_modified = false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::createFile(const string &file)
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
if (m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "file \"" << m_file.path() << "\" currently open; close first" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_file.setPath(file);
|
|
|
|
try {
|
2015-04-22 19:30:09 +02:00
|
|
|
try {
|
2017-06-10 22:26:12 +02:00
|
|
|
m_file.create();
|
|
|
|
m_file.generateRootEntry();
|
|
|
|
m_currentEntry = m_file.rootEntry();
|
|
|
|
m_o << "file \"" << file << "\" created and opened" << endl;
|
|
|
|
} catch (...) {
|
|
|
|
const char *what = catchIoFailure();
|
|
|
|
m_o << "IO error occured when creating file \"" << file << "\"" << endl;
|
|
|
|
throw ios_base::failure(what);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
} catch (const exception &e) {
|
|
|
|
if (*e.what() != 0) {
|
|
|
|
m_o << e.what() << endl;
|
|
|
|
}
|
|
|
|
m_file.clear();
|
|
|
|
m_currentEntry = nullptr;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
m_modified = false;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::changePassphrase()
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
if (m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "can not set passphrase; no file opened or created" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
m_file.setPassword(askForPassphrase(true));
|
|
|
|
m_modified = true;
|
|
|
|
m_o << "passphrase changed; use save to apply" << endl;
|
|
|
|
} catch (const runtime_error &) {
|
|
|
|
m_o << "passphrase has not changed" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::removePassphrase()
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "nothing to remove; no file opened or created" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (*m_file.password()) {
|
|
|
|
m_file.clearPassword();
|
|
|
|
m_o << "passphrase removed; use save to apply" << endl;
|
|
|
|
m_modified = true;
|
|
|
|
} else {
|
|
|
|
m_o << "nothing to remove; no passphrase present on current file" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::pwd()
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "no file open" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
auto path = m_currentEntry->path();
|
|
|
|
m_o << path.front() << ": /";
|
|
|
|
path.pop_front();
|
|
|
|
m_o << joinStrings(path, "/") << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::cd(const string &path)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "can not change directory; no file open" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (Entry *entry = resolvePath(path)) {
|
|
|
|
m_currentEntry = entry;
|
|
|
|
m_o << "changed to \"" << entry->label() << "\"" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::ls()
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
|
|
|
m_o << "can not list any entires; no file open" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (m_currentEntry->type()) {
|
|
|
|
case EntryType::Account: {
|
|
|
|
m_o << "fields:";
|
|
|
|
for (const Field &field : static_cast<AccountEntry *>(m_currentEntry)->fields()) {
|
|
|
|
m_o << "\n" << field.name();
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case EntryType::Node: {
|
|
|
|
m_o << "entries:";
|
|
|
|
for (const Entry *entry : static_cast<NodeEntry *>(m_currentEntry)->children()) {
|
|
|
|
m_o << "\n" << entry->label();
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
break;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
}
|
|
|
|
m_o << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::tree()
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "can not print tree; no file open" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
function<void(const Entry *entry, unsigned char level)> printEntries;
|
|
|
|
printEntries = [&printEntries, this](const Entry *entry, unsigned char level) {
|
|
|
|
for (unsigned char i = 0; i < level; ++i) {
|
|
|
|
m_o << " ";
|
|
|
|
}
|
|
|
|
m_o << entry->label() << endl;
|
|
|
|
if (entry->type() == EntryType::Node) {
|
|
|
|
for (const Entry *child : (static_cast<const NodeEntry *>(entry)->children())) {
|
|
|
|
printEntries(child, level + 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
printEntries(m_currentEntry, 0);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::makeEntry(EntryType entryType, const string &label)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
|
|
|
m_o << "can not make entry; no file open" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (m_currentEntry->type()) {
|
|
|
|
case EntryType::Node:
|
|
|
|
switch (entryType) {
|
2015-04-22 19:30:09 +02:00
|
|
|
case EntryType::Node:
|
2017-06-10 22:26:12 +02:00
|
|
|
m_o << "node entry \"" << (new NodeEntry(label, static_cast<NodeEntry *>(m_currentEntry)))->label() << "\" created" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
break;
|
|
|
|
case EntryType::Account:
|
2017-06-10 22:26:12 +02:00
|
|
|
m_o << "account entry \"" << (new AccountEntry(label, static_cast<NodeEntry *>(m_currentEntry)))->label() << "\" created" << endl;
|
|
|
|
break;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
m_modified = true;
|
|
|
|
break;
|
|
|
|
case EntryType::Account:
|
|
|
|
m_o << "can not make entry; current entry is no node entry" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::removeEntry(const string &path)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
|
|
|
m_o << "can not remove entry; no file open" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (Entry *entry = resolvePath(path)) {
|
|
|
|
if (entry == m_file.rootEntry()) {
|
|
|
|
m_o << "can not remove root entry" << endl;
|
|
|
|
} else {
|
|
|
|
if (entry == m_currentEntry) {
|
|
|
|
m_currentEntry = entry->parent();
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
m_o << "removed entry \"" << entry->label() << "\"" << endl;
|
|
|
|
delete entry;
|
|
|
|
m_modified = true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::renameEntry(const string &path)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "can not rename entry; no file open" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (Entry *entry = resolvePath(path)) {
|
|
|
|
string label;
|
|
|
|
m_o << "enter new name: " << endl;
|
|
|
|
getline(m_i, label);
|
|
|
|
if (label.empty()) {
|
|
|
|
m_o << "can not rename; new name is empty" << endl;
|
|
|
|
} else {
|
|
|
|
entry->setLabel(label);
|
|
|
|
m_o << "entry renamed to \"" << entry->label() << "\"" << endl;
|
|
|
|
m_modified = true;
|
|
|
|
}
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::moveEntry(const string &path)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
|
|
|
m_o << "can not rename entry; no file open" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (Entry *entry = resolvePath(path)) {
|
|
|
|
string newParentPath;
|
|
|
|
m_o << "enter path of new parent: " << endl;
|
|
|
|
getline(m_i, newParentPath);
|
|
|
|
if (newParentPath.empty()) {
|
|
|
|
m_o << "can not move; path of new parent is empty" << endl;
|
|
|
|
} else {
|
|
|
|
if (Entry *newParent = resolvePath(newParentPath)) {
|
|
|
|
switch (newParent->type()) {
|
|
|
|
case EntryType::Account:
|
|
|
|
m_o << "can not move; new parent must be a node entry" << endl;
|
|
|
|
break;
|
|
|
|
case EntryType::Node:
|
|
|
|
if (entry->parent() == entry) {
|
|
|
|
m_o << "element not moved; parent doesn't change" << endl;
|
|
|
|
} else if (entry->type() == EntryType::Node && newParent->isIndirectChildOf(static_cast<NodeEntry *>(entry))) {
|
|
|
|
m_o << "can not move; new parent mustn't be child of the entry to move" << endl;
|
|
|
|
} else {
|
|
|
|
entry->setParent(static_cast<NodeEntry *>(newParent));
|
|
|
|
m_o << "entry moved to \"" << newParent->label() << "\"" << endl;
|
|
|
|
m_modified = true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::readField(const string &fieldName)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "can not read field; no file open" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m_currentEntry->type() != EntryType::Account) {
|
|
|
|
m_o << "can not read field; current entry is no account entry" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const vector<Field> &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
|
|
|
|
bool valuesFound = false;
|
|
|
|
for (const Field &field : fields) {
|
|
|
|
if (field.name() == fieldName) {
|
|
|
|
m_o << field.value() << endl;
|
|
|
|
valuesFound = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!valuesFound) {
|
|
|
|
m_o << "field \"" << fieldName << "\" does not exist" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::setField(bool useMuter, const string &fieldName)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
|
|
|
m_o << "can not set field; no file open" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m_currentEntry->type() != EntryType::Account) {
|
|
|
|
m_o << "can not set field; current entry is no account entry" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
vector<Field> &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
|
|
|
|
unsigned int valuesFound = 0;
|
|
|
|
string value;
|
|
|
|
m_o << "enter new value: ";
|
|
|
|
if (useMuter) {
|
|
|
|
InputMuter m;
|
|
|
|
getline(m_i, value);
|
|
|
|
m_o << endl << "repeat: ";
|
|
|
|
string repeat;
|
|
|
|
getline(m_i, repeat);
|
|
|
|
if (value != repeat) {
|
|
|
|
m_o << "values do not match; field has not been altered" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
getline(m_i, value);
|
|
|
|
}
|
|
|
|
for (Field &field : fields) {
|
|
|
|
if (field.name() == fieldName) {
|
|
|
|
++valuesFound;
|
|
|
|
if (valuesFound == 1) {
|
|
|
|
field.setValue(value);
|
2015-04-22 19:30:09 +02:00
|
|
|
} else {
|
2017-06-10 22:26:12 +02:00
|
|
|
m_o << "enter new value for " << valuesFound << ". field (with the specified field): ";
|
|
|
|
value.clear();
|
|
|
|
if (useMuter) {
|
|
|
|
InputMuter m;
|
|
|
|
getline(m_i, value);
|
|
|
|
m_o << endl << "repeat: ";
|
|
|
|
string repeat;
|
|
|
|
getline(m_i, repeat);
|
|
|
|
if (value == repeat) {
|
2015-04-22 19:30:09 +02:00
|
|
|
field.setValue(value);
|
|
|
|
} else {
|
2017-06-10 22:26:12 +02:00
|
|
|
m_o << "values do not match; field has not been altered" << endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
} else {
|
|
|
|
getline(m_i, value);
|
|
|
|
field.setValue(value);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
field.setType(useMuter ? FieldType::Password : FieldType::Normal);
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
2017-06-10 22:26:12 +02:00
|
|
|
}
|
|
|
|
switch (valuesFound) {
|
|
|
|
case 0:
|
|
|
|
fields.emplace_back(static_cast<AccountEntry *>(m_currentEntry), fieldName, value);
|
|
|
|
if (useMuter) {
|
|
|
|
fields.back().setType(FieldType::Password);
|
|
|
|
}
|
|
|
|
m_o << "new field with value inserted" << endl;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
m_o << "value updated" << endl;
|
|
|
|
m_modified = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
m_o << valuesFound << " values updated" << endl;
|
|
|
|
m_modified = true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::removeField(const string &fieldName)
|
|
|
|
{
|
2017-06-10 22:26:12 +02:00
|
|
|
if (!m_file.isOpen()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "can not remove field; no file open" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m_currentEntry->type() != EntryType::Account) {
|
|
|
|
m_o << "can not remove field; current entry is no account entry" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
vector<Field> &fields = static_cast<AccountEntry *>(m_currentEntry)->fields();
|
|
|
|
unsigned int valuesFound = 0;
|
|
|
|
for (const Field &field : fields) {
|
|
|
|
if (field.name() == fieldName) {
|
|
|
|
++valuesFound;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch (valuesFound) {
|
|
|
|
case 0:
|
|
|
|
m_o << "can not remove field; specified field \"" << fieldName << "\" not found" << endl;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
fields.erase(remove_if(fields.begin(), fields.end(), [&fieldName](Field &field) { return field.name() == fieldName; }));
|
|
|
|
m_o << "field removed" << endl;
|
|
|
|
m_modified = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
valuesFound = 0;
|
|
|
|
fields.erase(remove_if(fields.begin(), fields.end(), [this, &fieldName, &valuesFound](Field &field) {
|
|
|
|
if (field.name() == fieldName) {
|
|
|
|
m_o << "remove " << ++valuesFound << ". occurrence? [y]=yes, different key=no " << endl;
|
|
|
|
string res;
|
|
|
|
getline(m_i, res);
|
|
|
|
return !(res == "y" || res == "yes");
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
m_o << valuesFound << " fields removed" << endl;
|
|
|
|
m_modified = true;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::printHelp()
|
|
|
|
{
|
2017-12-17 23:38:12 +01:00
|
|
|
m_o << TextAttribute::Bold << "Command: Description: \n"
|
|
|
|
<< TextAttribute::Reset
|
|
|
|
<< "quit,q quits the application\n"
|
|
|
|
"q! forces the application to quit\n"
|
|
|
|
"wq saves the current file and quits the application\n"
|
|
|
|
"clear,c clears the console\n"
|
|
|
|
"\n"
|
|
|
|
"create,cr creates a new file at the specified path\n"
|
|
|
|
"openreadonly opens the specified file (read-only)\n"
|
|
|
|
"open,o opens the specified file\n"
|
|
|
|
"close,cl closes the currently opened file\n"
|
2015-04-22 19:30:09 +02:00
|
|
|
"\n"
|
2017-12-17 23:38:12 +01:00
|
|
|
"save,w saves the currently opened file\n"
|
|
|
|
"chpassphrase changes the passphrase\n"
|
|
|
|
"rmpassphrase removes the passphrase\n"
|
2015-04-22 19:30:09 +02:00
|
|
|
"\n"
|
2017-12-17 23:38:12 +01:00
|
|
|
"pwd prints the path of the current entry\n"
|
|
|
|
"cd changes the current entry\n"
|
|
|
|
"ls lists the entries/fields of the current entry\n"
|
|
|
|
"tree,t shows all child entries of the current entry\n"
|
|
|
|
"mknode,mkn creates a node entry with the specified label in the current entry\n"
|
|
|
|
"mkaccount,mka creates an account entry with the specified label in the current entry\n"
|
|
|
|
"rmentry,rme removes the entry specified by its path\n"
|
|
|
|
"rnentry,rne renames the entry specified by its path\n"
|
|
|
|
"mventry,me moves the entry specified by its path\n"
|
2015-04-22 19:30:09 +02:00
|
|
|
"\n"
|
2017-12-17 23:38:12 +01:00
|
|
|
"readfield,rf reads the specified field of the current account\n"
|
|
|
|
"setfield,sf sets the specified field of the current account\n"
|
|
|
|
"setfieldpw,sp sets the specified password field of the current account\n"
|
|
|
|
"rmfield,rf removes the specified field of the current account\n"
|
2017-05-01 03:26:04 +02:00
|
|
|
<< endl;
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InteractiveCli::quit()
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
if (m_file.isOpen() && m_modified) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "file modified; use q! or wq" << endl;
|
|
|
|
} else {
|
|
|
|
m_quit = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string InteractiveCli::askForPassphrase(bool confirm)
|
|
|
|
{
|
2017-05-01 03:26:04 +02:00
|
|
|
if (confirm) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "enter new passphrase: ";
|
|
|
|
} else {
|
|
|
|
m_o << "enter passphrase: ";
|
|
|
|
}
|
|
|
|
m_o.flush();
|
|
|
|
string input1;
|
|
|
|
{
|
|
|
|
InputMuter m;
|
|
|
|
getline(m_i, input1);
|
|
|
|
}
|
|
|
|
m_o << endl;
|
2017-05-01 03:26:04 +02:00
|
|
|
if (input1.empty()) {
|
2015-04-22 19:30:09 +02:00
|
|
|
m_o << "you did not enter a passphrase" << endl;
|
2017-06-10 22:26:12 +02:00
|
|
|
return input1;
|
|
|
|
}
|
|
|
|
if (confirm) {
|
|
|
|
m_o << "confirm new passphrase: ";
|
|
|
|
m_o.flush();
|
|
|
|
string input2;
|
|
|
|
{
|
|
|
|
InputMuter m;
|
|
|
|
getline(m_i, input2);
|
|
|
|
}
|
|
|
|
m_o << endl;
|
|
|
|
if (input1 != input2) {
|
|
|
|
m_o << "phrases do not match" << endl;
|
|
|
|
throw runtime_error("confirmation failed");
|
2015-04-22 19:30:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return input1;
|
|
|
|
}
|
2017-09-29 17:17:12 +02:00
|
|
|
} // namespace Cli
|