File indexing completed on 2024-06-23 05:14:18

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     utils/output.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "output.h"
0013 
0014 #include "cached.h"
0015 #include "detail_p.h"
0016 #include "input_p.h"
0017 #include "kdpipeiodevice.h"
0018 #include "kleo_assert.h"
0019 #include "log.h"
0020 #include "overwritedialog.h"
0021 
0022 #include <Libkleo/KleoException>
0023 
0024 #include <KFileUtils>
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 
0028 #include "kleopatra_debug.h"
0029 
0030 #include <QApplication>
0031 #include <QBuffer>
0032 #include <QClipboard>
0033 #include <QDir>
0034 #include <QFileInfo>
0035 #include <QPointer>
0036 #include <QProcess>
0037 #include <QString>
0038 #include <QTemporaryFile>
0039 #include <QTimer>
0040 #include <QUrl>
0041 #include <QWidget>
0042 
0043 #ifdef Q_OS_WIN
0044 #include <windows.h>
0045 #endif
0046 
0047 #include <cerrno>
0048 
0049 using namespace Kleo;
0050 using namespace Kleo::_detail;
0051 
0052 static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1; // no timeout
0053 static const int PROCESS_TERMINATE_TIMEOUT = 5 * 1000; // 5s
0054 
0055 class OverwritePolicy::Private
0056 {
0057 public:
0058     Private(QWidget *p, OverwritePolicy::Options options_, OverwritePolicy::Policy pol)
0059         : policy(pol)
0060         , parentWidget(p)
0061         , options{options_}
0062     {
0063     }
0064 
0065     OverwritePolicy::Policy policy;
0066     QWidget *parentWidget;
0067     OverwritePolicy::Options options;
0068 };
0069 
0070 OverwritePolicy::OverwritePolicy(Policy initialPolicy)
0071     : d{new Private{nullptr, {}, initialPolicy}}
0072 {
0073 }
0074 
0075 OverwritePolicy::OverwritePolicy(QWidget *parent, OverwritePolicy::Options options)
0076     : d{new Private{parent, options, Ask}}
0077 {
0078 }
0079 
0080 OverwritePolicy::~OverwritePolicy() = default;
0081 
0082 OverwritePolicy::Policy OverwritePolicy::policy() const
0083 {
0084     return d->policy;
0085 }
0086 
0087 void OverwritePolicy::setPolicy(Policy policy)
0088 {
0089     d->policy = policy;
0090 }
0091 
0092 namespace
0093 {
0094 
0095 class TemporaryFile : public QTemporaryFile
0096 {
0097 public:
0098     using QTemporaryFile::QTemporaryFile;
0099 
0100     void close() override
0101     {
0102         if (isOpen()) {
0103             m_oldFileName = fileName();
0104         }
0105         QTemporaryFile::close();
0106     }
0107 
0108     bool openNonInheritable()
0109     {
0110         if (!QTemporaryFile::open()) {
0111             return false;
0112         }
0113 #if defined(Q_OS_WIN)
0114         // QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable.
0115         // The handle is then inherited by gpgsm, which prevents deletion of the temp file
0116         // in FileOutput::doFinalize()
0117         // There are no inheritable handles under wince
0118         return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0);
0119 #endif
0120         return true;
0121     }
0122 
0123     QString oldFileName() const
0124     {
0125         return m_oldFileName;
0126     }
0127 
0128 private:
0129     QString m_oldFileName;
0130 };
0131 
0132 template<typename T_IODevice>
0133 struct inhibit_close : T_IODevice {
0134     explicit inhibit_close()
0135         : T_IODevice()
0136     {
0137     }
0138     template<typename T1>
0139     explicit inhibit_close(T1 &t1)
0140         : T_IODevice(t1)
0141     {
0142     }
0143 
0144     /* reimp */ void close() override
0145     {
0146     }
0147     void reallyClose()
0148     {
0149         T_IODevice::close();
0150     }
0151 };
0152 
0153 template<typename T_IODevice>
0154 struct redirect_close : T_IODevice {
0155     explicit redirect_close()
0156         : T_IODevice()
0157         , m_closed(false)
0158     {
0159     }
0160     template<typename T1>
0161     explicit redirect_close(T1 &t1)
0162         : T_IODevice(t1)
0163         , m_closed(false)
0164     {
0165     }
0166 
0167     /* reimp */ void close() override
0168     {
0169         this->closeWriteChannel();
0170         m_closed = true;
0171     }
0172 
0173     bool isClosed() const
0174     {
0175         return m_closed;
0176     }
0177 
0178 private:
0179     bool m_closed;
0180 };
0181 
0182 class FileOutput;
0183 class OutputInput : public InputImplBase
0184 {
0185 public:
0186     OutputInput(const std::shared_ptr<FileOutput> &output);
0187 
0188     unsigned int classification() const override
0189     {
0190         return 0;
0191     }
0192 
0193     void outputFinalized()
0194     {
0195         if (!m_ioDevice->open(QIODevice::ReadOnly)) {
0196             qCCritical(KLEOPATRA_LOG) << "Failed to open file for reading";
0197         }
0198     }
0199 
0200     std::shared_ptr<QIODevice> ioDevice() const override
0201     {
0202         return m_ioDevice;
0203     }
0204 
0205     unsigned long long size() const override
0206     {
0207         return 0;
0208     }
0209 
0210 private:
0211     std::shared_ptr<FileOutput> m_output;
0212     mutable std::shared_ptr<QIODevice> m_ioDevice = nullptr;
0213 };
0214 
0215 class OutputImplBase : public Output
0216 {
0217 public:
0218     OutputImplBase()
0219         : Output()
0220         , m_defaultLabel()
0221         , m_customLabel()
0222         , m_errorString()
0223         , m_isFinalized(false)
0224         , m_isFinalizing(false)
0225         , m_cancelPending(false)
0226         , m_canceled(false)
0227         , m_binaryOpt(false)
0228     {
0229     }
0230 
0231     QString label() const override
0232     {
0233         return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel;
0234     }
0235     void setLabel(const QString &label) override
0236     {
0237         m_customLabel = label;
0238     }
0239     void setDefaultLabel(const QString &l)
0240     {
0241         m_defaultLabel = l;
0242     }
0243     void setBinaryOpt(bool value) override
0244     {
0245         m_binaryOpt = value;
0246     }
0247     bool binaryOpt() const override
0248     {
0249         return m_binaryOpt;
0250     }
0251 
0252     QString errorString() const override
0253     {
0254         if (m_errorString.dirty()) {
0255             m_errorString = doErrorString();
0256         }
0257         return m_errorString;
0258     }
0259 
0260     bool isFinalized() const override
0261     {
0262         return m_isFinalized;
0263     }
0264     void finalize() override
0265     {
0266         qCDebug(KLEOPATRA_LOG) << this;
0267         if (m_isFinalized || m_isFinalizing) {
0268             return;
0269         }
0270         m_isFinalizing = true;
0271         try {
0272             doFinalize();
0273         } catch (...) {
0274             m_isFinalizing = false;
0275             throw;
0276         }
0277         m_isFinalizing = false;
0278         m_isFinalized = true;
0279         if (m_cancelPending) {
0280             cancel();
0281         }
0282     }
0283 
0284     void cancel() override
0285     {
0286         qCDebug(KLEOPATRA_LOG) << this;
0287         if (m_isFinalizing) {
0288             m_cancelPending = true;
0289         } else if (!m_canceled) {
0290             m_isFinalizing = true;
0291             try {
0292                 doCancel();
0293             } catch (...) {
0294             }
0295             m_isFinalizing = false;
0296             m_isFinalized = false;
0297             m_canceled = true;
0298         }
0299     }
0300 
0301 private:
0302     virtual QString doErrorString() const
0303     {
0304         if (std::shared_ptr<QIODevice> io = ioDevice()) {
0305             return io->errorString();
0306         } else {
0307             return i18n("No output device");
0308         }
0309     }
0310     virtual void doFinalize() = 0;
0311     virtual void doCancel() = 0;
0312 
0313 private:
0314     QString m_defaultLabel;
0315     QString m_customLabel;
0316     mutable cached<QString> m_errorString;
0317     bool m_isFinalized : 1;
0318     bool m_isFinalizing : 1;
0319     bool m_cancelPending : 1;
0320     bool m_canceled : 1;
0321     bool m_binaryOpt : 1;
0322 };
0323 
0324 class PipeOutput : public OutputImplBase
0325 {
0326 public:
0327     explicit PipeOutput(assuan_fd_t fd);
0328 
0329     std::shared_ptr<QIODevice> ioDevice() const override
0330     {
0331         return m_io;
0332     }
0333     void doFinalize() override
0334     {
0335         m_io->reallyClose();
0336     }
0337     void doCancel() override
0338     {
0339         doFinalize();
0340     }
0341 
0342 private:
0343     std::shared_ptr<inhibit_close<KDPipeIODevice>> m_io;
0344 };
0345 
0346 class ProcessStdInOutput : public OutputImplBase
0347 {
0348 public:
0349     explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd);
0350 
0351     std::shared_ptr<QIODevice> ioDevice() const override
0352     {
0353         return m_proc;
0354     }
0355     void doFinalize() override
0356     {
0357         /*
0358           Make sure the data is written in the output here. If this
0359           is not done the output will be written in small chunks
0360           trough the eventloop causing an unnecessary delay before
0361           the process has even a chance to work and finish.
0362           This delay is mainly noticeable on Windows where it can
0363           take ~30 seconds to write out a 10MB file in the 512 byte
0364           chunks gpgme serves. */
0365         qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite() << " Bytes to be written";
0366         while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT))
0367             ;
0368 
0369         if (!m_proc->isClosed()) {
0370             m_proc->close();
0371         }
0372         m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT);
0373     }
0374 
0375     bool failed() const override
0376     {
0377         if (!m_proc) {
0378             return false;
0379         }
0380         return !(m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0);
0381     }
0382 
0383     void doCancel() override
0384     {
0385         m_proc->terminate();
0386         QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill);
0387     }
0388     QString label() const override;
0389 
0390 private:
0391     QString doErrorString() const override;
0392 
0393 private:
0394     const QString m_command;
0395     const QStringList m_arguments;
0396     const std::shared_ptr<redirect_close<QProcess>> m_proc;
0397 };
0398 
0399 class FileOutput : public OutputImplBase
0400 {
0401 public:
0402     explicit FileOutput(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy);
0403     ~FileOutput() override
0404     {
0405         qCDebug(KLEOPATRA_LOG) << this;
0406     }
0407 
0408     QString label() const override
0409     {
0410         return QFileInfo(m_fileName).fileName();
0411     }
0412     std::shared_ptr<QIODevice> ioDevice() const override
0413     {
0414         return m_tmpFile;
0415     }
0416     void doFinalize() override;
0417     void doCancel() override
0418     {
0419         qCDebug(KLEOPATRA_LOG) << this;
0420     }
0421     QString fileName() const override
0422     {
0423         return m_fileName;
0424     }
0425 
0426     void attachInput(const std::shared_ptr<OutputInput> &input)
0427     {
0428         m_attachedInput = std::weak_ptr<OutputInput>(input);
0429     }
0430 
0431 private:
0432     QString m_fileName;
0433     std::shared_ptr<TemporaryFile> m_tmpFile;
0434     const std::shared_ptr<OverwritePolicy> m_policy;
0435     std::weak_ptr<OutputInput> m_attachedInput;
0436 };
0437 
0438 #ifndef QT_NO_CLIPBOARD
0439 class ClipboardOutput : public OutputImplBase
0440 {
0441 public:
0442     explicit ClipboardOutput(QClipboard::Mode mode);
0443 
0444     QString label() const override;
0445     std::shared_ptr<QIODevice> ioDevice() const override
0446     {
0447         return m_buffer;
0448     }
0449     void doFinalize() override;
0450     void doCancel() override
0451     {
0452     }
0453 
0454 private:
0455     QString doErrorString() const override
0456     {
0457         return QString();
0458     }
0459 
0460 private:
0461     const QClipboard::Mode m_mode;
0462     std::shared_ptr<QBuffer> m_buffer;
0463 };
0464 #endif // QT_NO_CLIPBOARD
0465 
0466 class ByteArrayOutput : public OutputImplBase
0467 {
0468 public:
0469     explicit ByteArrayOutput(QByteArray *data)
0470         : m_buffer(std::shared_ptr<QBuffer>(new QBuffer(data)))
0471     {
0472         if (!m_buffer->open(QIODevice::WriteOnly))
0473             throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not open bytearray for writing?!"));
0474     }
0475 
0476     QString label() const override
0477     {
0478         return m_label;
0479     }
0480 
0481     void setLabel(const QString &label) override
0482     {
0483         m_label = label;
0484     }
0485 
0486     std::shared_ptr<QIODevice> ioDevice() const override
0487     {
0488         return m_buffer;
0489     }
0490 
0491     void doFinalize() override
0492     {
0493         m_buffer->close();
0494     }
0495 
0496     void doCancel() override
0497     {
0498         m_buffer->close();
0499     }
0500 
0501 private:
0502     QString doErrorString() const override
0503     {
0504         return QString();
0505     }
0506 
0507 private:
0508     QString m_label;
0509     std::shared_ptr<QBuffer> m_buffer;
0510 };
0511 
0512 }
0513 
0514 std::shared_ptr<Output> Output::createFromPipeDevice(assuan_fd_t fd, const QString &label)
0515 {
0516     std::shared_ptr<PipeOutput> po(new PipeOutput(fd));
0517     po->setDefaultLabel(label);
0518     return po;
0519 }
0520 
0521 PipeOutput::PipeOutput(assuan_fd_t fd)
0522     : OutputImplBase()
0523     , m_io(new inhibit_close<KDPipeIODevice>)
0524 {
0525     errno = 0;
0526     if (!m_io->open(fd, QIODevice::WriteOnly))
0527         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open FD %1 for writing", assuanFD2int(fd)));
0528 }
0529 
0530 std::shared_ptr<Output> Output::createFromFile(const QString &fileName, bool forceOverwrite)
0531 {
0532     return createFromFile(fileName, std::make_shared<OverwritePolicy>(forceOverwrite ? OverwritePolicy::Overwrite : OverwritePolicy::Skip));
0533 }
0534 
0535 std::shared_ptr<Output> Output::createFromFile(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy)
0536 {
0537     std::shared_ptr<FileOutput> fo(new FileOutput(fileName, policy));
0538     qCDebug(KLEOPATRA_LOG) << fo.get();
0539     return fo;
0540 }
0541 
0542 FileOutput::FileOutput(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy)
0543     : OutputImplBase()
0544     , m_fileName(fileName)
0545     , m_tmpFile(new TemporaryFile(fileName))
0546     , m_policy(policy)
0547 {
0548     Q_ASSERT(m_policy);
0549     errno = 0;
0550     if (!m_tmpFile->openNonInheritable())
0551         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not create temporary file for output \"%1\"", fileName));
0552 }
0553 
0554 static QString suggestFileName(const QString &fileName)
0555 {
0556     const QFileInfo fileInfo{fileName};
0557     const QString path = fileInfo.absolutePath();
0558     const QString newFileName = KFileUtils::suggestName(QUrl::fromLocalFile(path), fileInfo.fileName());
0559     return path + QLatin1Char{'/'} + newFileName;
0560 }
0561 
0562 QString OverwritePolicy::obtainOverwritePermission(const QString &fileName)
0563 {
0564     switch (d->policy) {
0565     case OverwritePolicy::Ask:
0566         break;
0567     case OverwritePolicy::Overwrite:
0568         return fileName;
0569     case OverwritePolicy::Rename: {
0570         return suggestFileName(fileName);
0571     }
0572     case OverwritePolicy::Skip:
0573         return {};
0574     case OverwritePolicy::Cancel:
0575         qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Called although user canceled operation";
0576         return {};
0577     }
0578 
0579     OverwriteDialog::Options options = OverwriteDialog::AllowRename;
0580     if (d->options & MultipleFiles) {
0581         options |= OverwriteDialog::MultipleItems | OverwriteDialog::AllowSkip;
0582     }
0583     OverwriteDialog dialog{d->parentWidget, i18nc("@title:window", "File Already Exists"), fileName, options};
0584     const auto result = static_cast<OverwriteDialog::Result>(dialog.exec());
0585     qCDebug(KLEOPATRA_LOG) << __func__ << "result:" << static_cast<int>(result);
0586     switch (result) {
0587     case OverwriteDialog::Cancel:
0588         d->policy = OverwritePolicy::Cancel;
0589         return {};
0590     case OverwriteDialog::AutoSkip:
0591         d->policy = OverwritePolicy::Skip;
0592         [[fallthrough]];
0593     case OverwriteDialog::Skip:
0594         return {};
0595     case OverwriteDialog::OverwriteAll:
0596         d->policy = OverwritePolicy::Overwrite;
0597         [[fallthrough]];
0598     case OverwriteDialog::Overwrite:
0599         return fileName;
0600     case OverwriteDialog::Rename:
0601         return dialog.newFileName();
0602     case OverwriteDialog::AutoRename: {
0603         d->policy = OverwritePolicy::Rename;
0604         return suggestFileName(fileName);
0605     }
0606     default:
0607         qCDebug(KLEOPATRA_LOG) << __func__ << "unexpected result:" << result;
0608     };
0609     return {};
0610 }
0611 
0612 void FileOutput::doFinalize()
0613 {
0614     qCDebug(KLEOPATRA_LOG) << this;
0615 
0616     struct Remover {
0617         QString file;
0618         ~Remover()
0619         {
0620             if (QFile::exists(file)) {
0621                 QFile::remove(file);
0622             }
0623         }
0624     } remover;
0625 
0626     kleo_assert(m_tmpFile);
0627 
0628     if (m_tmpFile->isOpen()) {
0629         m_tmpFile->close();
0630     }
0631 
0632     QString tmpFileName = m_tmpFile->oldFileName();
0633     remover.file = tmpFileName;
0634 
0635     m_tmpFile->setAutoRemove(false);
0636     QPointer<QObject> guard = m_tmpFile.get();
0637     m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/
0638     kleo_assert(!guard); // if this triggers, we need to audit for holder of std::shared_ptr<QIODevice>s.
0639 
0640     const QFileInfo fi(tmpFileName);
0641     if (!fi.exists()) {
0642         /* QT Bug 83365 since qt 5.13 causes the filename of temporary files
0643          * in UNC path name directories (unmounted samba shares) to start with
0644          * UNC/ instead of the // that Qt can actually handle for things like
0645          * rename and remove. So if we can't find our temporary file we try
0646          * to workaround that bug. */
0647         qCDebug(KLEOPATRA_LOG) << "failure to find " << tmpFileName;
0648         if (tmpFileName.startsWith(QLatin1StringView("UNC"))) {
0649             tmpFileName.replace(0, strlen("UNC"), QLatin1Char('/'));
0650             remover.file = tmpFileName;
0651         }
0652         const QFileInfo fi2(tmpFileName);
0653         if (!fi2.exists()) {
0654             throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not find temporary file \"%1\".").arg(tmpFileName));
0655         }
0656     }
0657 
0658     qCDebug(KLEOPATRA_LOG) << this << "renaming" << tmpFileName << "->" << m_fileName;
0659     if (QFile::rename(tmpFileName, m_fileName)) {
0660         qCDebug(KLEOPATRA_LOG) << this << "renaming succeeded";
0661 
0662         if (!m_attachedInput.expired()) {
0663             m_attachedInput.lock()->outputFinalized();
0664         }
0665         return;
0666     }
0667 
0668     qCDebug(KLEOPATRA_LOG) << this << "renaming failed";
0669 
0670     if (QFile::exists(m_fileName)) {
0671         const auto newFileName = m_policy->obtainOverwritePermission(m_fileName);
0672         if (newFileName.isEmpty()) {
0673             throw Exception(gpg_error(GPG_ERR_CANCELED), i18n("Overwriting declined"));
0674         }
0675         if (newFileName == m_fileName) {
0676             qCDebug(KLEOPATRA_LOG) << this << "going to remove file for overwriting" << m_fileName;
0677             if (!QFile::remove(m_fileName)) {
0678                 throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
0679                                 xi18nc("@info", "Could not remove file <filename>%1</filename> for overwriting.", m_fileName));
0680             }
0681             qCDebug(KLEOPATRA_LOG) << this << "removing file to overwrite succeeded";
0682         } else {
0683             m_fileName = newFileName;
0684         }
0685     }
0686 
0687     qCDebug(KLEOPATRA_LOG) << this << "renaming" << tmpFileName << "->" << m_fileName;
0688     if (QFile::rename(tmpFileName, m_fileName)) {
0689         qCDebug(KLEOPATRA_LOG) << this << "renaming succeeded";
0690 
0691         if (!m_attachedInput.expired()) {
0692             m_attachedInput.lock()->outputFinalized();
0693         }
0694         return;
0695     }
0696 
0697     qCDebug(KLEOPATRA_LOG) << this << "renaming failed";
0698 
0699     throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n(R"(Could not rename file "%1" to "%2")", tmpFileName, m_fileName));
0700 }
0701 
0702 std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command)
0703 {
0704     return std::shared_ptr<Output>(new ProcessStdInOutput(command, QStringList(), QDir::current()));
0705 }
0706 
0707 std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args)
0708 {
0709     return std::shared_ptr<Output>(new ProcessStdInOutput(command, args, QDir::current()));
0710 }
0711 
0712 std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd)
0713 {
0714     return std::shared_ptr<Output>(new ProcessStdInOutput(command, args, wd));
0715 }
0716 
0717 ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd)
0718     : OutputImplBase()
0719     , m_command(cmd)
0720     , m_arguments(args)
0721     , m_proc(new redirect_close<QProcess>)
0722 {
0723     qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << '\n' << cmd << args;
0724     if (cmd.isEmpty())
0725         throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Command not specified"));
0726     m_proc->setWorkingDirectory(wd.absolutePath());
0727     m_proc->start(cmd, args);
0728     m_proc->setReadChannel(QProcess::StandardError);
0729     if (!m_proc->waitForStarted())
0730         throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not start %1 process: %2", cmd, m_proc->errorString()));
0731 }
0732 
0733 QString ProcessStdInOutput::label() const
0734 {
0735     if (!m_proc) {
0736         return OutputImplBase::label();
0737     }
0738     // output max. 3 arguments
0739     const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' '));
0740     if (m_arguments.size() > 3) {
0741         return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline);
0742     } else {
0743         return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline);
0744     }
0745 }
0746 
0747 QString ProcessStdInOutput::doErrorString() const
0748 {
0749     kleo_assert(m_proc);
0750     if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) {
0751         return QString();
0752     }
0753     if (m_proc->error() == QProcess::UnknownError)
0754         return i18n("Error while running %1: %2", m_command, QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData()));
0755     else {
0756         return i18n("Failed to execute %1: %2", m_command, m_proc->errorString());
0757     }
0758 }
0759 
0760 #ifndef QT_NO_CLIPBOARD
0761 std::shared_ptr<Output> Output::createFromClipboard()
0762 {
0763     return std::shared_ptr<Output>(new ClipboardOutput(QClipboard::Clipboard));
0764 }
0765 
0766 ClipboardOutput::ClipboardOutput(QClipboard::Mode mode)
0767     : OutputImplBase()
0768     , m_mode(mode)
0769     , m_buffer(new QBuffer)
0770 {
0771     errno = 0;
0772     if (!m_buffer->open(QIODevice::WriteOnly))
0773         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not write to clipboard"));
0774 }
0775 
0776 QString ClipboardOutput::label() const
0777 {
0778     switch (m_mode) {
0779     case QClipboard::Clipboard:
0780         return i18n("Clipboard");
0781     case QClipboard::FindBuffer:
0782         return i18n("Find buffer");
0783     case QClipboard::Selection:
0784         return i18n("Selection");
0785     }
0786     return QString();
0787 }
0788 
0789 void ClipboardOutput::doFinalize()
0790 {
0791     if (m_buffer->isOpen()) {
0792         m_buffer->close();
0793     }
0794     if (QClipboard *const cb = QApplication::clipboard()) {
0795         cb->setText(QString::fromUtf8(m_buffer->data()));
0796     } else
0797         throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not find clipboard"));
0798 }
0799 #endif // QT_NO_CLIPBOARD
0800 
0801 Output::~Output()
0802 {
0803 }
0804 
0805 OutputInput::OutputInput(const std::shared_ptr<FileOutput> &output)
0806     : m_output(output)
0807     , m_ioDevice(new QFile(output->fileName()))
0808 {
0809 }
0810 
0811 std::shared_ptr<Input> Input::createFromOutput(const std::shared_ptr<Output> &output)
0812 {
0813     if (auto fo = std::dynamic_pointer_cast<FileOutput>(output)) {
0814         auto input = std::shared_ptr<OutputInput>(new OutputInput(fo));
0815         fo->attachInput(input);
0816         return input;
0817     } else {
0818         return {};
0819     }
0820 }
0821 
0822 std::shared_ptr<Output> Output::createFromByteArray(QByteArray *data, const QString &label)
0823 {
0824     auto ret = std::shared_ptr<ByteArrayOutput>(new ByteArrayOutput(data));
0825     ret->setLabel(label);
0826     return ret;
0827 }