From b11e71d331310c27fde3e53a098ba108bc8f0f61 Mon Sep 17 00:00:00 2001 From: tezeb Date: Sun, 4 Dec 2016 22:43:17 +0100 Subject: Use new executor in Pass --- imitatepass.cpp | 168 +++++++++++++++++++++++--------------- imitatepass.h | 13 ++- mainwindow.cpp | 246 ++++++++++++++++++++++++++++++-------------------------- mainwindow.h | 12 ++- pass.cpp | 176 ++++++++++------------------------------ pass.h | 55 ++++++------- qtpass.pro | 8 +- realpass.cpp | 90 ++++++++++++++------- realpass.h | 9 ++- 9 files changed, 393 insertions(+), 384 deletions(-) diff --git a/imitatepass.cpp b/imitatepass.cpp index ace52ff7..65f1aceb 100644 --- a/imitatepass.cpp +++ b/imitatepass.cpp @@ -1,47 +1,80 @@ #include "imitatepass.h" +#include "debughelper.h" #include "mainwindow.h" #include "qtpasssettings.h" ImitatePass::ImitatePass() {} +void ImitatePass::executeWrapper(int id, const QString &app, + const QStringList &args, bool readStdout, + bool readStderr) { + executeWrapper(id, app, args, QString(), readStdout, readStderr); +} + +void ImitatePass::executeWrapper(int id, const QString &app, + const QStringList &args, QString input, + bool readStdout, bool readStderr) { + QString d; + for (auto &i : args) + d += " " + i; + dbg() << app << d; + exec.execute(id, QtPassSettings::getPassStore(), app, args, input, readStdout, + readStderr); +} + /** * @brief ImitatePass::GitInit git init wrapper */ void ImitatePass::GitInit() { - executeWrapper(QtPassSettings::getGitExecutable(), - "init \"" + QtPassSettings::getPassStore() + '"'); + executeWrapper(GIT_INIT, QtPassSettings::getGitExecutable(), + {"init", QtPassSettings::getPassStore()}); } /** * @brief ImitatePass::GitPull git init wrapper */ void ImitatePass::GitPull() { - executeWrapper(QtPassSettings::getGitExecutable(), "pull"); + executeWrapper(GIT_PULL, QtPassSettings::getGitExecutable(), {"pull"}); +} + +/** + * @brief ImitatePass::GitPull_b git pull wrapper + */ +void ImitatePass::GitPull_b() { + exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"}); } /** * @brief ImitatePass::GitPush git init wrapper */ void ImitatePass::GitPush() { - executeWrapper(QtPassSettings::getGitExecutable(), "push"); + executeWrapper(GIT_PUSH, QtPassSettings::getGitExecutable(), {"push"}); } /** - * @brief ImitatePass::Show git init wrapper + * @brief ImitatePass::Show shows content of file */ -QProcess::ExitStatus ImitatePass::Show(QString file, bool block) { +void ImitatePass::Show(QString file) { // TODO(bezet): apparently not yet needed // file += ".gpg"; - executeWrapper(QtPassSettings::getGpgExecutable(), - "-d --quiet --yes --no-encrypt-to --batch --use-agent \"" + - file + '"'); - if (block) - return waitForProcess(); - return QProcess::NormalExit; + QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to", + "--batch", "--use-agent", file}; + executeWrapper(PASS_SHOW, QtPassSettings::getGpgExecutable(), args); } /** - * @brief ImitatePass::Insert git init wrapper + * @brief ImitatePass::Show_b show content of file, blocking version + * + * @returns process exitCode + */ +int ImitatePass::Show_b(QString file) { + QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to", + "--batch", "--use-agent", file}; + return exec.executeBlocking(QtPassSettings::getGpgExecutable(), args); +} + +/** + * @brief ImitatePass::Insert create new file with encrypted content * * @param file file to be created * @param value value to be stored in file @@ -50,7 +83,7 @@ QProcess::ExitStatus ImitatePass::Show(QString file, bool block) { void ImitatePass::Insert(QString file, QString newValue, bool overwrite) { file += ".gpg"; // TODO(bezet): getRecipientString is in MainWindow for now - fix this ;) - QString recipients = Pass::getRecipientString(file, " -r "); + QStringList recipients = Pass::getRecipientList(file); if (recipients.isEmpty()) { // TODO(bezet): probably throw here emit critical(tr("Can not edit"), @@ -58,36 +91,43 @@ void ImitatePass::Insert(QString file, QString newValue, bool overwrite) { "file missing or invalid.")); return; } - QString force(overwrite ? " --yes " : " "); - executeWrapper(QtPassSettings::getGpgExecutable(), - force + "--batch -eq --output \"" + file + "\" " + recipients + - " -", + QStringList args = {"--batch", "-eq", "--output", file}; + for (auto &r : recipients) { + args.append("-r"); + args.append(r); + }; + if (overwrite) + args.append("--yes"); + args.append("-"); + executeWrapper(PASS_INSERT, QtPassSettings::getGpgExecutable(), args, newValue); if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) { if (!overwrite) - executeWrapper(QtPassSettings::getGitExecutable(), "add \"" + file + '"'); + executeWrapper(GIT_ADD, QtPassSettings::getGitExecutable(), + {"add", file}); QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file); path.replace(QRegExp("\\.gpg$"), ""); - executeWrapper(QtPassSettings::getGitExecutable(), - "commit \"" + file + "\" -m \"" + - (overwrite ? "Edit" : "Add") + " for " + path + - " using QtPass.\""); + QString msg = QString(overwrite ? "Edit" : "\"Add") + " for " + path + + " using QtPass."; + GitCommit(file, msg); } } +void ImitatePass::GitCommit(const QString &file, const QString &msg) { + executeWrapper(GIT_COMMIT, QtPassSettings::getGitExecutable(), + {"commit", "-m", msg, "--", file}); +} + /** * @brief ImitatePass::Remove git init wrapper */ void ImitatePass::Remove(QString file, bool isDir) { if (QtPassSettings::isUseGit()) { - executeWrapper(QtPassSettings::getGitExecutable(), - QString("rm ") + (isDir ? "-rf " : "-f ") + '"' + file + - '"'); + executeWrapper(GIT_RM, QtPassSettings::getGitExecutable(), + {"rm", (isDir ? "-rf" : "-f"), file}); // TODO(bezet): commit message used to have pass-like file name inside(ie. // getFile(file, true) - executeWrapper(QtPassSettings::getGitExecutable(), - "commit \"" + file + "\" -m \"Remove for " + file + - " using QtPass.\""); + GitCommit(file, "Remove for " + file + " using QtPass."); } else { if (isDir) { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) @@ -105,7 +145,8 @@ void ImitatePass::Remove(QString file, bool isDir) { * @brief ImitatePass::Init initialize pass repository * * @param path path in which new password-store will be created - * @param users list of users who shall be able to decrypt passwords in path + * @param users list of users who shall be able to decrypt passwords in + * path */ void ImitatePass::Init(QString path, const QList &users) { QString gpgIdFile = path + ".gpg-id"; @@ -140,13 +181,11 @@ void ImitatePass::Init(QString path, const QList &users) { if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit() && !QtPassSettings::getGitExecutable().isEmpty()) { if (addFile) - executeWrapper(QtPassSettings::getGitExecutable(), - "add \"" + gpgIdFile + '"'); + executeWrapper(GIT_ADD, QtPassSettings::getGitExecutable(), + {"add", gpgIdFile}); QString path = gpgIdFile; path.replace(QRegExp("\\.gpg$"), ""); - executeWrapper(QtPassSettings::getGitExecutable(), - "commit \"" + gpgIdFile + "\" -m \"Added " + path + - " using QtPass.\""); + GitCommit(gpgIdFile, "Added " + path + " using QtPass."); } reencryptPath(path); } @@ -191,10 +230,8 @@ void ImitatePass::reencryptPath(QString dir) { if (QtPassSettings::isAutoPull()) { // TODO(bezet): move statuses inside actions? emit statusMsg(tr("Updating password-store"), 2000); - GitPull(); + GitPull_b(); } - waitFor(50); - process.waitForFinished(); QDir currentDir; QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files, QDirIterator::Subdirectories); @@ -205,16 +242,14 @@ void ImitatePass::reencryptPath(QString dir) { gpgId = getRecipientList(fileName); gpgId.sort(); } - process.waitForFinished(); - executeWrapper(QtPassSettings::getGpgExecutable(), - "-v --no-secmem-warning " - "--no-permission-warning --list-only " - "--keyid-format long " + - fileName); - process.waitForFinished(3000); + // TODO(bezet): enable --with-colons for better future-proofness? + QStringList args = { + "-v", "--no-secmem-warning", "--no-permission-warning", + "--list-only", "--keyid-format=long", fileName}; + QString keys, err; + exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err); QStringList actualKeys; - QString keys = - process.readAllStandardOutput() + process.readAllStandardError(); + keys += err; QStringList key = keys.split("\n"); QListIterator itr(key); while (itr.hasNext()) { @@ -230,14 +265,13 @@ void ImitatePass::reencryptPath(QString dir) { actualKeys.sort(); if (actualKeys != gpgId) { // dbg()<< actualKeys << gpgId << getRecipientList(fileName); - dbg()<< "reencrypt " << fileName << " for " << gpgId; + dbg() << "reencrypt " << fileName << " for " << gpgId; QString local_lastDecrypt = "Could not decrypt"; emit lastDecrypt(local_lastDecrypt); - executeWrapper(QtPassSettings::getGpgExecutable(), - "-d --quiet --yes --no-encrypt-to --batch --use-agent \"" + - fileName + '"'); - process.waitForFinished(30000); // long wait (passphrase stuff) - local_lastDecrypt = process.readAllStandardOutput(); + args = QStringList{"-d", "--quiet", "--yes", "--no-encrypt-to", + "--batch", "--use-agent", fileName}; + exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, + &local_lastDecrypt); emit lastDecrypt(local_lastDecrypt); if (!local_lastDecrypt.isEmpty() && @@ -246,33 +280,35 @@ void ImitatePass::reencryptPath(QString dir) { local_lastDecrypt += "\n"; emit lastDecrypt(local_lastDecrypt); - QString recipients = getRecipientString(fileName, " -r "); + QStringList recipients = Pass::getRecipientList(fileName); if (recipients.isEmpty()) { emit critical(tr("Can not edit"), tr("Could not read encryption key to use, .gpg-id " "file missing or invalid.")); return; } - executeWrapper(QtPassSettings::getGpgExecutable(), - "--yes --batch -eq --output \"" + fileName + "\" " + - recipients + " -", - local_lastDecrypt); - process.waitForFinished(3000); + args = QStringList{"--yes", "--batch", "-eq", "--output", fileName}; + for (auto &i : recipients) { + args.append("-r"); + args.append(i); + } + args.append("-"); + exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, + local_lastDecrypt); if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) { - executeWrapper(QtPassSettings::getGitExecutable(), - "add \"" + fileName + '"'); + exec.executeBlocking(QtPassSettings::getGitExecutable(), + {"add", fileName}); QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName); path.replace(QRegExp("\\.gpg$"), ""); - executeWrapper(QtPassSettings::getGitExecutable(), - "commit \"" + fileName + "\" -m \"" + "Edit for " + - path + " using QtPass.\""); - process.waitForFinished(3000); + exec.executeBlocking(QtPassSettings::getGitExecutable(), + {"commit", fileName, "-m", + "Edit for " + path + " using QtPass."}); } } else { - dbg()<< "Decrypt error on re-encrypt"; + dbg() << "Decrypt error on re-encrypt"; } } } diff --git a/imitatepass.h b/imitatepass.h index cc56cb62..8d2fd253 100644 --- a/imitatepass.h +++ b/imitatepass.h @@ -8,13 +8,24 @@ class ImitatePass : public Pass { bool removeDir(const QString &dirName); + void executeWrapper(int id, const QString &app, const QStringList &args, + bool readStdout = true, bool readStderr = true); + + void executeWrapper(int id, const QString &app, const QStringList &args, + QString input, bool readStdout = true, + bool readStderr = true); + + void GitCommit(const QString &file, const QString &msg); + public: ImitatePass(); virtual ~ImitatePass() {} virtual void GitInit() override; virtual void GitPull() override; + virtual void GitPull_b() override; virtual void GitPush() override; - virtual QProcess::ExitStatus Show(QString file, bool block = false) override; + virtual void Show(QString file) override; + virtual int Show_b(QString file) override; virtual void Insert(QString file, QString value, bool overwrite = false) override; virtual void Remove(QString file, bool isDir = false) override; diff --git a/mainwindow.cpp b/mainwindow.cpp index 92deb47c..c63a27f1 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -35,14 +35,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), fusedav(this), keygen(NULL), tray(NULL), pass(nullptr) { - // connect(process.data(), SIGNAL(readyReadStandardOutput()), this, - // SLOT(readyRead())); - // TODO(bezet): this should be reconnected dynamically when pass changes connect(&rpass, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); - connect(&rpass, SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(processFinished(int, QProcess::ExitStatus))); + connect(&rpass, SIGNAL(finished(int, const QString &, const QString &)), this, + SLOT(processFinished(int, const QString &, const QString &))); connect(&rpass, SIGNAL(startingExecuteWrapper()), this, SLOT(executeWrapperStarted())); connect(&rpass, SIGNAL(statusMsg(QString, int)), this, @@ -52,8 +49,8 @@ MainWindow::MainWindow(QWidget *parent) connect(&ipass, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); - connect(&ipass, SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(processFinished(int, QProcess::ExitStatus))); + connect(&ipass, SIGNAL(finished(int, const QString &, const QString &)), this, + SLOT(processFinished(int, const QString &, const QString &))); connect(&ipass, SIGNAL(startingExecuteWrapper()), this, SLOT(executeWrapperStarted())); connect(&ipass, SIGNAL(statusMsg(QString, int)), this, @@ -69,7 +66,6 @@ MainWindow::MainWindow(QWidget *parent) ui->setupUi(this); enableUiElements(true); - execQueue = new QQueue; ui->statusBar->showMessage(tr("Welcome to QtPass %1").arg(VERSION), 2000); freshStart = true; startupPhase = true; @@ -525,11 +521,12 @@ void MainWindow::config() { /** * @brief MainWindow::on_updateButton_clicked do a git pull */ -// TODO(bezet): add bool block and wait for process to finish -void MainWindow::on_updateButton_clicked() { +void MainWindow::on_updateButton_clicked(bool block) { ui->statusBar->showMessage(tr("Updating password-store"), 2000); - currentAction = GIT; - pass->GitPull(); + if (block) + pass->GitPull_b(); + else + pass->GitPull(); } /** @@ -659,114 +656,139 @@ void MainWindow::executeWrapperStarted() { /** * @brief MainWindow::readyRead we have data */ -void MainWindow::readyRead(bool finished = false) { - if (currentAction == PWGEN) +void MainWindow::readyRead(const QString &p_output, const QString &p_errout) { + QString output = p_output; + QString error = p_errout; + if (currentAction == PWGEN) { return; - QString output = ""; - QString error = ""; - if (currentAction != GPG_INTERNAL) { - error = pass->readAllStandardError(); - QByteArray processOutBytes = pass->readAllStandardOutput(); - QTextCodec *codec = QTextCodec::codecForLocale(); - output = codec->toUnicode(processOutBytes); - if (finished && currentAction == GPG) { - lastDecrypt = output; - QStringList tokens = output.split("\n"); - QString password = tokens.at(0); - - if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER && - !output.isEmpty()) { - clippedText = tokens[0]; - if (QtPassSettings::getClipBoardType() == Enums::CLIPBOARD_ALWAYS) - copyTextToClipboard(tokens[0]); - if (QtPassSettings::isUseAutoclearPanel()) { - clearPanelTimer.start(); - } - if (QtPassSettings::isHidePassword() && - !QtPassSettings::isUseTemplate()) { - tokens[0] = "***" + tr("Password hidden") + "***"; - output = tokens.join("\n"); - } - if (QtPassSettings::isHideContent()) - output = "***" + tr("Content hidden") + "***"; - } - - if (QtPassSettings::isUseTemplate() && !QtPassSettings::isHideContent()) { - while (ui->gridLayout->count() > 0) { - QLayoutItem *item = ui->gridLayout->takeAt(0); - delete item->widget(); - delete item; - } - QStringList remainingTokens; - for (int j = 1; j < tokens.length(); ++j) { - QString token = tokens.at(j); - if (token.contains(':')) { - int colon = token.indexOf(':'); - QString field = token.left(colon); - if (QtPassSettings::isTemplateAllFields() || - QtPassSettings::getPassTemplate().contains(field)) { - QString value = token.right(token.length() - colon - 1); - if (!QtPassSettings::getPassTemplate().contains(field) && - value.startsWith("//")) { - remainingTokens.append(token); - continue; // colon is probably from a url - } - addToGridLayout(j, field, value); - } - } else { - remainingTokens.append(token); - } - } - if (ui->gridLayout->count() == 0) - ui->verticalLayoutPassword->setSpacing(0); - else - ui->verticalLayoutPassword->setSpacing(6); - output = remainingTokens.join("\n"); - } else { - clearTemplateWidgets(); - } - if (!QtPassSettings::isHideContent() && !password.isEmpty()) { - // now set the password. If we set it earlier, the layout will be - // cleared - addToGridLayout(0, tr("Password"), password); - } - if (QtPassSettings::isUseAutoclearPanel()) { - clearPanelTimer.start(); - } - } - output.replace(QRegExp("<"), "<"); - output.replace(QRegExp(">"), ">"); - output.replace(QRegExp(" "), " "); - } else { + } else if (currentAction == GPG) { + passShowHandler(p_output); + } else if (currentAction == GPG_INTERNAL) { // qDebug() << process->readAllStandardOutput(); // qDebug() << process->readAllStandardError(); - if (finished && 0 != keygen) { + if (0 != keygen) { qDebug() << "Keygen Done"; keygen->close(); keygen = 0; // TODO(annejan) some sanity checking ? } + } else { + DisplayInTextBrowser(p_output); } - if (!error.isEmpty()) { - if (currentAction == GIT) { - // https://github.com/IJHack/qtpass/issues/111 - output = "" + error + "
" + - output; + processErrorExit(p_errout); +} + +void MainWindow::passShowHandler(const QString &p_output) { + QString output = p_output; + lastDecrypt = p_output; + { + QStringList tokens = p_output.split("\n"); + QString password = tokens.at(0); + + if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER && + !p_output.isEmpty()) { + clippedText = tokens[0]; + if (QtPassSettings::getClipBoardType() == Enums::CLIPBOARD_ALWAYS) + copyTextToClipboard(tokens[0]); + if (QtPassSettings::isUseAutoclearPanel()) { + clearPanelTimer.start(); + } + if (QtPassSettings::isHidePassword() && + !QtPassSettings::isUseTemplate()) { + tokens[0] = "***" + tr("Password hidden") + "***"; + output = tokens.join("\n"); + } + if (QtPassSettings::isHideContent()) + output = "***" + tr("Content hidden") + "***"; + } + + if (QtPassSettings::isUseTemplate() && !QtPassSettings::isHideContent()) { + while (ui->gridLayout->count() > 0) { + QLayoutItem *item = ui->gridLayout->takeAt(0); + delete item->widget(); + delete item; + } + QStringList remainingTokens; + for (int j = 1; j < tokens.length(); ++j) { + QString token = tokens.at(j); + if (token.contains(':')) { + int colon = token.indexOf(':'); + QString field = token.left(colon); + if (QtPassSettings::isTemplateAllFields() || + QtPassSettings::getPassTemplate().contains(field)) { + QString value = token.right(token.length() - colon - 1); + if (!QtPassSettings::getPassTemplate().contains(field) && + value.startsWith("//")) { + remainingTokens.append(token); + continue; // colon is probably from a url + } + addToGridLayout(j, field, value); + } + } else { + remainingTokens.append(token); + } + } + if (ui->gridLayout->count() == 0) + ui->verticalLayoutPassword->setSpacing(0); + else + ui->verticalLayoutPassword->setSpacing(6); + output = remainingTokens.join("\n"); } else { - output = - "" + error + "
" + output; + clearTemplateWidgets(); + } + if (!QtPassSettings::isHideContent() && !password.isEmpty()) { + // now set the password. If we set it earlier, the layout will be + // cleared + addToGridLayout(0, tr("Password"), password); + } + if (QtPassSettings::isUseAutoclearPanel()) { + clearPanelTimer.start(); } } + DisplayInTextBrowser(output); +} + +void MainWindow::DisplayInTextBrowser(QString output, QString prefix, + QString postfix) { + + output.replace(QRegExp("<"), "<"); + output.replace(QRegExp(">"), ">"); + output.replace(QRegExp(" "), " "); + output.replace(QRegExp("((?:https?|ftp|ssh)://\\S+)"), "\\1"); output.replace(QRegExp("\n"), "
"); + output = prefix + output + postfix; if (!ui->textBrowser->toPlainText().isEmpty()) output = ui->textBrowser->toHtml() + output; ui->textBrowser->setHtml(output); } +void MainWindow::processErrorExit(const QString &p_error) { + if (!p_error.isEmpty()) { + QString output; + QString error = p_error; + error.replace(QRegExp("<"), "<"); + error.replace(QRegExp(">"), ">"); + error.replace(QRegExp(" "), " "); + if (currentAction == GIT) { + // https://github.com/IJHack/qtpass/issues/111 + output = "" + error + "
"; + } else { + output = "" + error + "
"; + } + + output.replace(QRegExp("((?:https?|ftp|ssh)://\\S+)"), + "\\1"); + output.replace(QRegExp("\n"), "
"); + if (!ui->textBrowser->toPlainText().isEmpty()) + output = ui->textBrowser->toHtml() + output; + ui->textBrowser->setHtml(output); + } +} + /** * @brief MainWindow::clearClipboard remove clipboard contents. */ @@ -799,23 +821,23 @@ void MainWindow::clearPanel(bool notify = true) { } /** - * @brief MainWindow::clearPanel because slots needs the same amout of params as - * signals + * @brief MainWindow::clearPanel because slots needs the same amout of params + * as signals */ void MainWindow::clearPanel() { clearPanel(true); } /** - * @brief MainWindow::processFinished process is finished, if there is another - * one queued up to run, start it. + * @brief MainWindow::processFinished background process has finished * @param exitCode * @param exitStatus + * @param output stdout from a process + * @param errout stderr from a process */ -void MainWindow::processFinished(int exitCode, - QProcess::ExitStatus exitStatus) { - bool error = exitStatus != QProcess::NormalExit || exitCode > 0; - readyRead(true); +void MainWindow::processFinished(int exitCode, const QString &output, + const QString &errout) { + readyRead(output, errout); enableUiElements(true); - if (!error && currentAction == EDIT) + if (exitCode == 0 && currentAction == EDIT) on_treeView_clicked(ui->treeView->currentIndex()); } @@ -869,8 +891,8 @@ void MainWindow::processError(QProcess::ProcessError error) { ui->textBrowser->setText(errorString); // TODO(bezet): this probably shall be done in finished handler(I guess it // finishes even on error) - if (pass->state() == QProcess::NotRunning) - enableUiElements(true); + // if (pass->state() == QProcess::NotRunning) + enableUiElements(true); } /** @@ -1421,9 +1443,7 @@ void MainWindow::addFolder() { */ void MainWindow::editPassword() { if (QtPassSettings::isUseGit() && QtPassSettings::isAutoPull()) - on_updateButton_clicked(); - pass->waitFor(30); - pass->waitForProcess(); + on_updateButton_clicked(true); // TODO(annejan) move to editbutton stuff possibly? currentDir = getDir(ui->treeView->currentIndex(), false); lastDecrypt = "Could not decrypt"; @@ -1431,7 +1451,7 @@ void MainWindow::editPassword() { getFile(ui->treeView->currentIndex(), QtPassSettings::isUsePass()); if (!file.isEmpty()) { currentAction = GPG; - if (pass->Show(file, true) == QProcess::NormalExit) + if (pass->Show_b(file) == 0) on_editButton_clicked(); } } diff --git a/mainwindow.h b/mainwindow.h index 64abe785..f09d4e98 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -65,13 +65,13 @@ public slots: void deselect(); private slots: - void on_updateButton_clicked(); + void on_updateButton_clicked(bool block = false); void on_pushButton_clicked(); void on_treeView_clicked(const QModelIndex &index); void on_treeView_doubleClicked(const QModelIndex &index); void on_configButton_clicked(); - void readyRead(bool finished); - void processFinished(int, QProcess::ExitStatus); + void readyRead(const QString &, const QString &); + void processFinished(int, const QString &, const QString &); void processError(QProcess::ProcessError); void clearClipboard(); void clearPanel(bool notify); @@ -97,6 +97,9 @@ private slots: void endReencryptPath(); void critical(QString, QString); void setLastDecrypt(QString); + void passShowHandler(const QString &); + + void processErrorExit(const QString &); private: QAction *actionAddPassword; @@ -114,7 +117,6 @@ private: QTimer clearClipboardTimer; actionType currentAction; QString lastDecrypt; - QQueue *execQueue; bool freshStart; QDialog *keygen; QString currentDir; @@ -141,6 +143,8 @@ private: void reencryptPath(QString dir); void addToGridLayout(int position, const QString &field, const QString &value); + void DisplayInTextBrowser(QString toShow, QString prefix = QString(), + QString postfix = QString()); }; #endif // MAINWINDOW_H_ diff --git a/pass.cpp b/pass.cpp index abbed7d4..b13b0da9 100644 --- a/pass.cpp +++ b/pass.cpp @@ -4,15 +4,14 @@ #include "util.h" #include -Pass::Pass() : wrapperRunning(false) { - connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), this, - SIGNAL(finished(int, QProcess::ExitStatus))); - connect(&process, SIGNAL(error(QProcess::ProcessError)), this, - SIGNAL(error(QProcess::ProcessError))); - connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(processFinished(int, QProcess::ExitStatus))); +Pass::Pass() : wrapperRunning(false), env(QProcess::systemEnvironment()) { + connect(&exec, SIGNAL(finished(int, const QString &, const QString &)), this, + SIGNAL(finished(int, const QString &, const QString &))); + // TODO(bezet): stop using process + // connect(&process, SIGNAL(error(QProcess::ProcessError)), this, + // SIGNAL(error(QProcess::ProcessError))); - env = QProcess::systemEnvironment(); + connect(&exec, &Executor::starting, this, &Pass::startingExecuteWrapper); #ifdef __APPLE__ // If it exists, add the gpgtools to PATH @@ -30,11 +29,6 @@ Pass::Pass() : wrapperRunning(false) { } } -QProcess::ExitStatus Pass::waitForProcess() { - process.waitForFinished(30000); - return process.exitStatus(); -} - /** * @brief Pass::Generate use either pwgen or internal password * generator @@ -46,22 +40,29 @@ QProcess::ExitStatus Pass::waitForProcess() { QString Pass::Generate(int length, const QString &charset) { QString passwd; if (QtPassSettings::isUsePwgen()) { - waitFor(2); // --secure goes first as it overrides --no-* otherwise - QString args = - QString("-1 ") + (QtPassSettings::isLessRandom() ? "" : "--secure ") + - (QtPassSettings::isAvoidCapitals() ? "--no-capitalize " - : "--capitalize ") + - (QtPassSettings::isAvoidNumbers() ? "--no-numerals " : "--numerals ") + - (QtPassSettings::isUseSymbols() ? "--symbols " : "") + - QString::number(length); - executeWrapper(QtPassSettings::getPwgenExecutable(), args); - process.waitForFinished(1000); - if (process.exitStatus() == QProcess::NormalExit) - passwd = - QString(process.readAllStandardOutput()).remove(QRegExp("[\\n\\r]")); - else - qDebug() << "pwgen fail"; + QStringList args; + args.append("-1"); + if (QtPassSettings::isLessRandom()) + args.append("--secure "); + args.append(QtPassSettings::isAvoidCapitals() ? "--no-capitalize " + : "--capitalize "); + args.append(QtPassSettings::isAvoidNumbers() ? "--no-numerals " + : "--numerals "); + if (QtPassSettings::isUseSymbols()) + args.append("--symbols "); + args.append(QString::number(length)); + QString p_out; + // TODO(bezet): try-catch here(2 statuses to merge o_O) + if (exec.executeBlocking(QtPassSettings::getPwgenExecutable(), args, + &passwd) == 0) + passwd.remove(QRegExp("[\\n\\r]")); + else { + passwd.clear(); + qDebug() << __FILE__ << ":" << __LINE__ << "\t" + << "pwgen fail"; + // TODO(bezet): emit critical ? + } } else { if (charset.length() > 0) { for (int i = 0; i < length; ++i) { @@ -81,12 +82,12 @@ QString Pass::Generate(int length, const QString &charset) { /** * @brief Pass::GenerateGPGKeys internal gpg keypair generator . . - * @param batch + * @param batch GnuPG style configuration string * @param keygenWindow */ void Pass::GenerateGPGKeys(QString batch) { - executeWrapper(QtPassSettings::getGpgExecutable(), - "--gen-key --no-tty --batch", batch); + exec.execute(PASSWD_GENERATE, QtPassSettings::getGpgExecutable(), + {"--gen-key", "--no-tty", "--batch"}, batch); // TODO check status / error messages // https://github.com/IJHack/QtPass/issues/202#issuecomment-251081688 } @@ -98,19 +99,16 @@ void Pass::GenerateGPGKeys(QString batch) { * @return QList users */ QList Pass::listKeys(QString keystring, bool secret) { - waitFor(5); QList users; - QString listopt = secret ? "--list-secret-keys " : "--list-keys "; - executeWrapper(QtPassSettings::getGpgExecutable(), - "--no-tty --with-colons " + listopt + keystring); - process.waitForFinished(2000); - if (process.exitStatus() != QProcess::NormalExit) + QStringList args = {"--no-tty", "--with-colons"}; + args.append(secret ? "--list-secret-keys" : "--list-keys"); + if (!keystring.isEmpty()) + args.append(keystring); + QString p_out; + if (exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &p_out) != + 0) return users; - QByteArray processOutBytes = process.readAllStandardOutput(); - QTextCodec *codec = QTextCodec::codecForLocale(); - QString processOutString = codec->toUnicode(processOutBytes); - QStringList keys = QString(processOutString) - .split(QRegExp("[\r\n]"), QString::SkipEmptyParts); + QStringList keys = p_out.split(QRegExp("[\r\n]"), QString::SkipEmptyParts); UserInfo current_user; foreach (QString key, keys) { QStringList props = key.split(':'); @@ -134,97 +132,6 @@ QList Pass::listKeys(QString keystring, bool secret) { return users; } -/** - * @brief Pass::waitFor wait until process.atEnd and execQueue.isEmpty - * or timeout after x-seconds - * - * @param seconds - */ -void Pass::waitFor(uint seconds) { - QDateTime current = QDateTime::currentDateTime(); - uint stop = current.toTime_t() + seconds; - while (!process.atEnd() || !execQueue.isEmpty()) { - current = QDateTime::currentDateTime(); - if (stop < current.toTime_t()) { - emit critical(tr("Timed out"), - tr("Can't start process, previous one is still running!")); - } - Util::qSleep(100); - } -} - -/** - * @brief Pass::processFinished process is finished, if there is another - * one queued up to run, start it. - * @param exitCode - * @param exitStatus - */ -void Pass::processFinished(int, QProcess::ExitStatus) { - wrapperRunning = false; - if (!execQueue.isEmpty()) { - execQueueItem item = execQueue.dequeue(); - executeWrapper(item.app, item.args, item.input); - } -} - -/** - * Temporary wrapper to ease refactoring, don't get used to it ;) - */ -QProcess::ProcessState Pass::state() { return process.state(); } - -/** - * @brief Pass::executeWrapper run an application, queue when needed. - * @param app path to application to run - * @param args required arguements - * @param input optional input - */ -void Pass::executeWrapper(QString app, QString args, QString input) { - // qDebug() << app + " " + args; - // Happens a lot if e.g. git binary is not set. - // This will result in bogus "QProcess::FailedToStart" messages, - // also hiding legitimate errors from the gpg commands. - if (app.isEmpty()) { - qDebug() << "Trying to execute nothing.."; - return; - } - // Convert to absolute path, just in case - app = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app); - if (wrapperRunning) { - execQueueItem item; - item.app = app; - item.args = args; - item.input = input; - - execQueue.enqueue(item); - qDebug() << item.app + "," + item.args + "," + item.input; - return; - } - wrapperRunning = true; - process.setWorkingDirectory(QtPassSettings::getPassStore()); - process.setEnvironment(env); - emit startingExecuteWrapper(); - process.start('"' + app + "\" " + args); - if (!input.isEmpty()) - process.write(input.toUtf8()); - process.closeWriteChannel(); -} - -/** - * @brief Pass::readAllStandardOutput temporary helper - * @return - */ -QByteArray Pass::readAllStandardOutput() { - return process.readAllStandardOutput(); -} - -/** - * @brief Pass::readAllStandardError temporary helper - * @return - */ -QByteArray Pass::readAllStandardError() { - return process.readAllStandardError(); -} - /** * @brief Pass::updateEnv update the execution environment (used when * switching profiles) @@ -242,6 +149,7 @@ void Pass::updateEnv() { env.replaceInStrings(store.first(), "PASSWORD_STORE_DIR=" + QtPassSettings::getPassStore()); } + exec.setEnvironment(env); } /** @@ -264,7 +172,7 @@ void Pass::resetPasswordStoreDir() { } /** - * @brief Pass::getRecipientList return list op gpg-id's to encrypt for + * @brief Pass::getRecipientList return list of gpg-id's to encrypt for * @param for_file which file (folder) would you like recepients for * @return recepients gpg-id contents */ diff --git a/pass.h b/pass.h index 5e535c92..8aeb51cb 100644 --- a/pass.h +++ b/pass.h @@ -3,48 +3,46 @@ #include "datahelpers.h" #include "enums.h" +#include "executor.h" #include #include #include #include -/*! - \struct execQueueItem - \brief Execution queue items for non-interactive ordered execution. - */ -struct execQueueItem { - /** - * @brief app executable path. - */ - QString app; - /** - * @brief args arguments for executable. - */ - QString args; - /** - * @brief input stdio input. - */ - QString input; -}; - class Pass : public QObject { Q_OBJECT - QQueue execQueue; bool wrapperRunning; QStringList env; protected: + Executor exec; void executeWrapper(QString, QString, QString = QString()); - QProcess process; + + enum PROCESS { + GIT_INIT = 0, + GIT_ADD, + GIT_COMMIT, + GIT_RM, + GIT_PULL, + GIT_PUSH, + PASS_SHOW, + PASS_INSERT, + PASS_REMOVE, + PASS_INIT, + PASSWD_GENERATE, + GPG_GENKEYS, + }; public: Pass(); virtual ~Pass() {} virtual void GitInit() = 0; virtual void GitPull() = 0; + virtual void GitPull_b() = 0; virtual void GitPush() = 0; - virtual QProcess::ExitStatus Show(QString file, bool block = false) = 0; + virtual void Show(QString file) = 0; + virtual int Show_b(QString file) = 0; virtual void Insert(QString file, QString value, bool force) = 0; virtual void Remove(QString file, bool isDir) = 0; virtual void Init(QString path, const QList &users) = 0; @@ -52,12 +50,6 @@ public: void GenerateGPGKeys(QString batch); QList listKeys(QString keystring = "", bool secret = false); - void waitFor(uint seconds); - QProcess::ProcessState state(); - QByteArray readAllStandardOutput(); - QByteArray readAllStandardError(); - // TODO(bezet): probably not needed in public interface(1 use MainWindow) - QProcess::ExitStatus waitForProcess(); void resetPasswordStoreDir(); void updateEnv(); // TODO(bezet): those are probably temporarly here @@ -65,15 +57,14 @@ public: static QString getRecipientString(QString for_file, QString separator = " ", int *count = NULL); -private slots: - void processFinished(int, QProcess::ExitStatus); - signals: - void finished(int exitCode, QProcess::ExitStatus); + void finished(int, const QString &output, const QString &errout); void error(QProcess::ProcessError); void startingExecuteWrapper(); void statusMsg(QString, int); void critical(QString, QString); + + void processErrorExit(int exitCode, const QString &err); }; #endif // PASS_H diff --git a/qtpass.pro b/qtpass.pro index fdae1205..64e23673 100644 --- a/qtpass.pro +++ b/qtpass.pro @@ -36,8 +36,9 @@ SOURCES += main.cpp\ qtpasssettings.cpp \ settingsconstants.cpp \ pass.cpp \ - realpass.cpp \ - imitatepass.cpp + realpass.cpp \ + imitatepass.cpp \ + executor.cpp HEADERS += mainwindow.h \ configdialog.h \ @@ -57,7 +58,8 @@ HEADERS += mainwindow.h \ realpass.h \ imitatepass.h \ datahelpers.h \ - debughelper.h + debughelper.h \ + executor.h FORMS += mainwindow.ui \ configdialog.ui \ diff --git a/realpass.cpp b/realpass.cpp index a8bab628..82222d4b 100644 --- a/realpass.cpp +++ b/realpass.cpp @@ -7,55 +7,88 @@ RealPass::RealPass() {} * @brief RealPass::executePass easy wrapper for running pass * @param args */ -void RealPass::executePass(QString args, QString input) { - executeWrapper(QtPassSettings::getPassExecutable(), args, input); +void RealPass::executePass(PROCESS id, const QStringList &args, QString input, + bool readStdout, bool readStderr) { + exec.execute(id, QtPassSettings::getPassExecutable(), args, input, readStdout, + readStderr); } /** - * @brief RealPass::GitInit git init wrapper + * @brief RealPass::executePass easy wrapper for running pass + * @param args + */ +void RealPass::executePass(PROCESS id, const QStringList &args, bool readStdout, + bool readStderr) { + exec.execute(id, QtPassSettings::getPassExecutable(), args, QString(), + readStdout, readStderr); +} + +/** + * @brief RealPass::GitInit pass git init wrapper + */ +void RealPass::GitInit() { executePass(GIT_INIT, {"git", "init"}); } + +/** + * @brief RealPass::GitInit pass git pull wrapper which blocks until process + * finishes + */ +void RealPass::GitPull_b() { + exec.executeBlocking(QtPassSettings::getPassExecutable(), {"git", "pull"}); +} + +/** + * @brief RealPass::GitPull pass git pull wrapper */ -void RealPass::GitInit() { executePass("git init"); } +void RealPass::GitPull() { executePass(GIT_PULL, {"git", "pull"}); } /** - * @brief RealPass::GitPull git init wrapper + * @brief RealPass::GitPush pass git push wrapper */ -void RealPass::GitPull() { executePass("git pull"); } +void RealPass::GitPush() { executePass(GIT_PUSH, {"git", "push"}); } /** - * @brief RealPass::GitPush git init wrapper + * @brief RealPass::Show pass show + * + * @param file file to decrypt + * + * @return if block is set, returns exit status of internal decryption + * process + * otherwise returns QProcess::NormalExit */ -void RealPass::GitPush() { executePass("git push"); } +void RealPass::Show(QString file) { + executePass(PASS_SHOW, {"show", file}, true); +} /** - * @brief RealPass::Show git init wrapper + * @brief RealPass::Show_b pass show * * @param file file to decrypt - * @param block wheater to wait for decryption process to finish * - * @return if block is set, returns exit status of internal decryption process + * @return if block is set, returns exit status of internal decryption + * process * otherwise returns QProcess::NormalExit */ -QProcess::ExitStatus RealPass::Show(QString file, bool block) { - executePass("show \"" + file + '"'); - if (block) - return waitForProcess(); - return QProcess::NormalExit; +int RealPass::Show_b(QString file) { + return exec.executeBlocking(QtPassSettings::getPassExecutable(), + {"show", file}); } /** - * @brief RealPass::Insert git init wrapper + * @brief RealPass::Insert pass insert */ void RealPass::Insert(QString file, QString newValue, bool overwrite) { - executePass(QString("insert ") + (overwrite ? "-f " : "") + "-m \"" + file + - '"', - newValue); + QStringList args = {"insert", "-m"}; + if (overwrite) + args.append("-f"); + args.append(file); + executePass(PASS_INSERT, args, newValue); } /** - * @brief RealPass::Remove git init wrapper + * @brief RealPass::Remove pass remove wrapper */ void RealPass::Remove(QString file, bool isDir) { - executePass(QString("rm ") + (isDir ? "-rf " : "-f ") + '"' + file + '"'); + executePass(PASS_REMOVE, {"rm", (isDir ? "-rf" : "-f"), file}); } /** @@ -65,16 +98,15 @@ void RealPass::Remove(QString file, bool isDir) { * @param users list of users with ability to decrypt new password-store */ void RealPass::Init(QString path, const QList &users) { - QString gpgIds = ""; - foreach (const UserInfo &user, users) { - if (user.enabled) { - gpgIds += user.key_id + " "; - } - } // remove the passStore directory otherwise, // pass would create a passStore/passStore/dir // but you want passStore/dir QString dirWithoutPassdir = path.remove(0, QtPassSettings::getPassStore().size()); - executePass("init --path=" + dirWithoutPassdir + " " + gpgIds); + QStringList args = {"init", "--path=" + dirWithoutPassdir}; + foreach (const UserInfo &user, users) { + if (user.enabled) + args.append(user.key_id); + } + executePass(PASS_INIT, args); } diff --git a/realpass.h b/realpass.h index a91a61d8..85ac0939 100644 --- a/realpass.h +++ b/realpass.h @@ -5,15 +5,20 @@ class RealPass : public Pass { private: - void executePass(QString args, QString input = QString()); + void executePass(PROCESS id, const QStringList &args, QString input, + bool readStdout = true, bool readStderr = false); + void executePass(PROCESS id, const QStringList &args, bool readStdout = true, + bool readStderr = false); public: RealPass(); virtual ~RealPass() {} virtual void GitInit() override; virtual void GitPull() override; + virtual void GitPull_b() override; virtual void GitPush() override; - virtual QProcess::ExitStatus Show(QString file, bool block = false) override; + virtual void Show(QString file) override; + virtual int Show_b(QString file) override; virtual void Insert(QString file, QString value, bool overwrite = false) override; virtual void Remove(QString file, bool isDir = false) override; -- cgit v1.2.3