diff --git a/lang/qt/src/Makefile.am b/lang/qt/src/Makefile.am index 5e16373e..ef625d89 100644 --- a/lang/qt/src/Makefile.am +++ b/lang/qt/src/Makefile.am @@ -36,6 +36,7 @@ qgpgme_sources = \ dataprovider.cpp \ debug.cpp \ decryptverifyarchivejob.cpp \ + decryptverifyjob.cpp \ encryptarchivejob.cpp \ filelistdataprovider.cpp \ job.cpp multideletejob.cpp qgpgmeadduseridjob.cpp \ @@ -180,6 +181,7 @@ private_qgpgme_headers = \ changeexpiryjob_p.h \ cleaner.h \ decryptverifyarchivejob_p.h \ + decryptverifyjob_p.h \ encryptarchivejob_p.h \ encryptjob_p.h \ importjob_p.h \ diff --git a/lang/qt/src/decryptverifyjob.cpp b/lang/qt/src/decryptverifyjob.cpp new file mode 100644 index 00000000..f369f1d5 --- /dev/null +++ b/lang/qt/src/decryptverifyjob.cpp @@ -0,0 +1,74 @@ +/* + decryptverifyjob.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2023 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME 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 of the + License, or (at your option) any later version. + + QGpgME 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "decryptverifyjob.h" +#include "decryptverifyjob_p.h" + +using namespace QGpgME; + +DecryptVerifyJob::DecryptVerifyJob(QObject *parent) + : Job{parent} +{ +} + +DecryptVerifyJob::~DecryptVerifyJob() = default; + +void DecryptVerifyJob::setInputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_inputFilePath = path; +} + +QString DecryptVerifyJob::inputFile() const +{ + auto d = jobPrivate(this); + return d->m_inputFilePath; +} + +void DecryptVerifyJob::setOutputFile(const QString &path) +{ + auto d = jobPrivate(this); + d->m_outputFilePath = path; +} + +QString DecryptVerifyJob::outputFile() const +{ + auto d = jobPrivate(this); + return d->m_outputFilePath; +} + +#include "decryptverifyjob.moc" diff --git a/lang/qt/src/decryptverifyjob.h b/lang/qt/src/decryptverifyjob.h index 8444e4d0..710fc80e 100644 --- a/lang/qt/src/decryptverifyjob.h +++ b/lang/qt/src/decryptverifyjob.h @@ -62,6 +62,14 @@ namespace QGpgME DecryptVerifyJob instance will have scheduled it's own destruction with a call to QObject::deleteLater(). + Alternatively, the job can be started with startIt() after setting + an input file and an output file. If the job is started this way then + the backend reads the input and writes the output directly from/to the + specified input file and output file. In this case the plainText value of + the result signal will always be empty. This direct IO mode is currently + only supported for OpenPGP. Note that startIt() does not schedule the job's + destruction if starting the job failed. + After result() is emitted, the DecryptVerifyJob will schedule it's own destruction by calling QObject::deleteLater(). */ @@ -71,7 +79,27 @@ class QGPGME_EXPORT DecryptVerifyJob : public Job protected: explicit DecryptVerifyJob(QObject *parent); public: - ~DecryptVerifyJob(); + ~DecryptVerifyJob() override; + + /** + * Sets the path of the file to decrypt (and verify). + * + * Used if the job is started with startIt(). + */ + void setInputFile(const QString &path); + QString inputFile() const; + + /** + * Sets the path of the file to write the result to. + * + * Used if the job is started with startIt(). + * + * \note If a file with this path exists, then the job will fail, i.e. you + * need to delete an existing file that shall be overwritten before you + * start the job. + */ + void setOutputFile(const QString &path); + QString outputFile() const; /** Starts the combined decryption and verification operation. diff --git a/lang/qt/src/decryptverifyjob_p.h b/lang/qt/src/decryptverifyjob_p.h new file mode 100644 index 00000000..bbd30b59 --- /dev/null +++ b/lang/qt/src/decryptverifyjob_p.h @@ -0,0 +1,50 @@ +/* + decryptverifyjob_p.h + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2023 g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME 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 of the + License, or (at your option) any later version. + + QGpgME 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __QGPGME_DECRYPTVERIFYJOB_P_H__ +#define __QGPGME_DECRYPTVERIFYJOB_P_H__ + +#include "job_p.h" + +namespace QGpgME +{ + +struct DecryptVerifyJobPrivate : public JobPrivate +{ + QString m_inputFilePath; + QString m_outputFilePath; +}; + +} + +#endif // __QGPGME_DECRYPTVERIFYJOB_P_H__ diff --git a/lang/qt/src/job.cpp b/lang/qt/src/job.cpp index 3c9422bb..56088ce3 100644 --- a/lang/qt/src/job.cpp +++ b/lang/qt/src/job.cpp @@ -44,7 +44,6 @@ #include "keylistjob.h" #include "listallkeysjob.h" #include "decryptjob.h" -#include "decryptverifyjob.h" #include "signkeyjob.h" #include "verifydetachedjob.h" #include "verifyopaquejob.h" @@ -162,7 +161,6 @@ void QGpgME::Job::startNow() make_job_subclass(KeyListJob) make_job_subclass(ListAllKeysJob) make_job_subclass(DecryptJob) -make_job_subclass(DecryptVerifyJob) make_job_subclass(SignKeyJob) make_job_subclass(VerifyDetachedJob) make_job_subclass(VerifyOpaqueJob) @@ -197,7 +195,6 @@ make_job_subclass(SetPrimaryUserIDJob) #include "keylistjob.moc" #include "listallkeysjob.moc" #include "decryptjob.moc" -#include "decryptverifyjob.moc" #include "signkeyjob.moc" #include "verifydetachedjob.moc" #include "verifyopaquejob.moc" diff --git a/lang/qt/src/qgpgmedecryptverifyjob.cpp b/lang/qt/src/qgpgmedecryptverifyjob.cpp index 256160b5..bc135147 100644 --- a/lang/qt/src/qgpgmedecryptverifyjob.cpp +++ b/lang/qt/src/qgpgmedecryptverifyjob.cpp @@ -39,25 +39,55 @@ #include "qgpgmedecryptverifyjob.h" #include "dataprovider.h" +#include "decryptverifyjob_p.h" +#include "util.h" -#include "context.h" -#include "decryptionresult.h" -#include "verificationresult.h" -#include "data.h" +#include +#include +#include +#include #include #include "qgpgme_debug.h" #include +#include #include using namespace QGpgME; using namespace GpgME; +namespace +{ + +class QGpgMEDecryptVerifyJobPrivate : public DecryptVerifyJobPrivate +{ + QGpgMEDecryptVerifyJob *q = nullptr; + +public: + QGpgMEDecryptVerifyJobPrivate(QGpgMEDecryptVerifyJob *qq) + : q{qq} + { + } + + ~QGpgMEDecryptVerifyJobPrivate() override = default; + +private: + GpgME::Error startIt() override; + + void startNow() override + { + q->run(); + } +}; + +} + QGpgMEDecryptVerifyJob::QGpgMEDecryptVerifyJob(Context *context) : mixin_type(context) { + setJobPrivate(this, std::unique_ptr{new QGpgMEDecryptVerifyJobPrivate{this}}); lateInitialization(); } @@ -112,6 +142,43 @@ static QGpgMEDecryptVerifyJob::result_type decrypt_verify_qba(Context *ctx, cons return decrypt_verify(ctx, nullptr, buffer, std::shared_ptr()); } +static QGpgMEDecryptVerifyJob::result_type decrypt_verify_from_filename(Context *ctx, + const QString &inputFilePath, + const QString &outputFilePath) +{ + Data indata; +#ifdef Q_OS_WIN + indata.setFileName(inputFilePath().toUtf8().constData()); +#else + indata.setFileName(QFile::encodeName(inputFilePath).constData()); +#endif + + PartialFileGuard partFileGuard{outputFilePath}; + if (partFileGuard.tempFileName().isEmpty()) { + return std::make_tuple(DecryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, VerificationResult{Error::fromCode(GPG_ERR_EEXIST)}, QByteArray{}, QString{}, Error{}); + } + + Data outdata; +#ifdef Q_OS_WIN + outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData()); +#else + outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData()); +#endif + + const auto results = ctx->decryptAndVerify(indata, outdata); + const auto &decryptionResult = results.first; + const auto &verificationResult = results.second; + + if (!decryptionResult.error().code() && !verificationResult.error().code()) { + // the operation succeeded -> save the result under the requested file name + partFileGuard.commit(); + } + + Error ae; + const QString log = _detail::audit_log_as_html(ctx, ae); + return std::make_tuple(decryptionResult, verificationResult, QByteArray{}, log, ae); +} + Error QGpgMEDecryptVerifyJob::start(const QByteArray &cipherText) { run(std::bind(&decrypt_verify_qba, std::placeholders::_1, cipherText)); @@ -132,10 +199,22 @@ QGpgME::QGpgMEDecryptVerifyJob::exec(const QByteArray &cipherText, QByteArray &p return mResult; } -//PENDING(marc) implement showErrorDialog() - void QGpgMEDecryptVerifyJob::resultHook(const result_type &tuple) { mResult = std::make_pair(std::get<0>(tuple), std::get<1>(tuple)); } + +GpgME::Error QGpgMEDecryptVerifyJobPrivate::startIt() +{ + if (m_inputFilePath.isEmpty() || m_outputFilePath.isEmpty()) { + return Error::fromCode(GPG_ERR_INV_VALUE); + } + + q->run([=](Context *ctx) { + return decrypt_verify_from_filename(ctx, m_inputFilePath, m_outputFilePath); + }); + + return {}; +} + #include "qgpgmedecryptverifyjob.moc" diff --git a/lang/qt/tests/Makefile.am b/lang/qt/tests/Makefile.am index 0007401c..df9c905f 100644 --- a/lang/qt/tests/Makefile.am +++ b/lang/qt/tests/Makefile.am @@ -89,6 +89,7 @@ t_wkdlookup_SOURCES = t-wkdlookup.cpp $(support_src) t_import_SOURCES = t-import.cpp $(support_src) t_revokekey_SOURCES = t-revokekey.cpp $(support_src) t_setprimaryuserid_SOURCES = t-setprimaryuserid.cpp $(support_src) +run_decryptverifyjob_SOURCES = run-decryptverifyjob.cpp run_decryptverifyarchivejob_SOURCES = run-decryptverifyarchivejob.cpp run_encryptarchivejob_SOURCES = run-encryptarchivejob.cpp run_encryptjob_SOURCES = run-encryptjob.cpp @@ -113,6 +114,7 @@ noinst_PROGRAMS = \ t-trustsignatures t-changeexpiryjob t-wkdlookup t-import t-revokekey \ t-setprimaryuserid \ run-decryptverifyarchivejob \ + run-decryptverifyjob \ run-encryptarchivejob \ run-encryptjob \ run-importjob run-exportjob run-receivekeysjob run-refreshkeysjob \ diff --git a/lang/qt/tests/run-decryptverifyjob.cpp b/lang/qt/tests/run-decryptverifyjob.cpp new file mode 100644 index 00000000..2ddfb1ae --- /dev/null +++ b/lang/qt/tests/run-decryptverifyjob.cpp @@ -0,0 +1,163 @@ +/* + run-decryptverifyjob.cpp + + This file is part of QGpgME's test suite. + Copyright (c) 2023 by g10 Code GmbH + Software engineering by Ingo Klöcker + + QGpgME is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License, + version 2, as published by the Free Software Foundation. + + QGpgME 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace GpgME; + +std::ostream &operator<<(std::ostream &os, const QString &s) +{ + return os << s.toLocal8Bit().constData(); +} + +struct CommandLineOptions { + QString inputFile; + QString outputFile; + std::chrono::seconds cancelTimeout{0}; +}; + +CommandLineOptions parseCommandLine(const QStringList &arguments) +{ + CommandLineOptions options; + + QCommandLineParser parser; + parser.setApplicationDescription("Test program for DecryptVerifyJob"); + parser.addHelpOption(); + parser.addOptions({ + {{"o", "output"}, "Write output to FILE.", "FILE"}, + {"cancel-after", "Cancel the running job after SECONDS seconds.", "SECONDS"}, + }); + parser.addPositionalArgument("file", "File to decrypt", "FILE"); + + parser.process(arguments); + + const auto args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(1); + } + + options.inputFile = args.front(); + options.outputFile = parser.value("output"); + if (parser.isSet("cancel-after")) { + bool ok; + options.cancelTimeout = std::chrono::seconds{parser.value("cancel-after").toInt(&ok)}; + if (!ok) { + options.cancelTimeout = std::chrono::seconds{-1}; + } + } + + return options; +} + +int main(int argc, char **argv) +{ + GpgME::initializeLibrary(); + + QCoreApplication app{argc, argv}; + app.setApplicationName("run-decryptverifyjob"); + + const auto options = parseCommandLine(app.arguments()); + if (options.cancelTimeout.count() < 0) { + std::cerr << "Ignoring invalid timeout for cancel." << std::endl; + } + + std::shared_ptr output; + if (options.outputFile.isEmpty() || options.outputFile == QLatin1String{"-"}) { + output.reset(new QFile); + output->open(stdout, QIODevice::WriteOnly); + } else { + if (QFile::exists(options.outputFile)) { + qCritical() << "File" << options.outputFile << "exists. Bailing out."; + return 1; + } + } + + auto job = QGpgME::openpgp()->decryptVerifyJob(); + if (!job) { + std::cerr << "Error: Could not create job" << std::endl; + return 1; + } + QObject::connect(job, + &QGpgME::DecryptVerifyJob::result, + &app, + [](const GpgME::DecryptionResult &decryptionResult, + const GpgME::VerificationResult &verificationResult, + const QByteArray &, + const QString &auditLog, + const GpgME::Error &) { + std::cerr << "Diagnostics: " << auditLog << std::endl; + std::cerr << "Decryption Result: " << decryptionResult << std::endl; + std::cerr << "Verification Result: " << verificationResult << std::endl; + qApp->quit(); + }); + if (options.cancelTimeout.count() > 0) { + QTimer::singleShot(options.cancelTimeout, job, [job]() { + std::cerr << "Canceling job" << std::endl; + job->slotCancel(); + }); + } + + std::shared_ptr input; + GpgME::Error err; + if (output) { + input.reset(new QFile{options.inputFile}); + input->open(QIODevice::ReadOnly); + job->start(input, output); + } else { + job->setInputFile(options.inputFile); + job->setOutputFile(options.outputFile); + err = job->startIt(); + } + if (err) { + std::cerr << "Error: Starting the job failed: " << err.asString() << std::endl; + return 1; + } + + return app.exec(); +}