1 /* -*- mode: c++; c-basic-offset:4 -*-
2     utils/output.cpp
3 
4     This file is part of Kleopatra, the KDE keymanager
5     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include <config-kleopatra.h>
11 
12 #include "output.h"
13 #include "input_p.h"
14 #include "detail_p.h"
15 #include "kleo_assert.h"
16 #include "kdpipeiodevice.h"
17 #include "log.h"
18 #include "cached.h"
19 
20 #include <Libkleo/KleoException>
21 
22 #include <KLocalizedString>
23 #include <KMessageBox>
24 #include "kleopatra_debug.h"
25 
26 #include <QFileInfo>
27 #include <QTemporaryFile>
28 #include <QString>
29 #include <QClipboard>
30 #include <QApplication>
31 #include <QBuffer>
32 #include <QPointer>
33 #include <QWidget>
34 #include <QDir>
35 #include <QProcess>
36 #include <QTimer>
37 
38 #ifdef Q_OS_WIN
39 # include <windows.h>
40 #endif
41 
42 #include <cerrno>
43 
44 using namespace Kleo;
45 using namespace Kleo::_detail;
46 
47 static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1;     // no timeout
48 static const int PROCESS_TERMINATE_TIMEOUT   = 5 * 1000; // 5s
49 
50 class OverwritePolicy::Private
51 {
52 public:
Private(QWidget * p,OverwritePolicy::Policy pol)53     Private(QWidget *p, OverwritePolicy::Policy pol) : policy(pol), widget(p) {}
54     OverwritePolicy::Policy policy;
55     QWidget *widget;
56 };
57 
OverwritePolicy(QWidget * parent,Policy initialPolicy)58 OverwritePolicy::OverwritePolicy(QWidget *parent, Policy initialPolicy) : d(new Private(parent, initialPolicy))
59 {
60 }
61 
~OverwritePolicy()62 OverwritePolicy::~OverwritePolicy() {}
63 
policy() const64 OverwritePolicy::Policy OverwritePolicy::policy() const
65 {
66     return d->policy;
67 }
68 
setPolicy(Policy policy)69 void OverwritePolicy::setPolicy(Policy policy)
70 {
71     d->policy = policy;
72 }
73 
parentWidget() const74 QWidget *OverwritePolicy::parentWidget() const
75 {
76     return d->widget;
77 }
78 
79 namespace
80 {
81 
82 class TemporaryFile : public QTemporaryFile
83 {
84 public:
TemporaryFile()85     explicit TemporaryFile() : QTemporaryFile() {}
TemporaryFile(const QString & templateName)86     explicit TemporaryFile(const QString &templateName) : QTemporaryFile(templateName) {}
TemporaryFile(QObject * parent)87     explicit TemporaryFile(QObject *parent) : QTemporaryFile(parent) {}
TemporaryFile(const QString & templateName,QObject * parent)88     explicit TemporaryFile(const QString &templateName, QObject *parent) : QTemporaryFile(templateName, parent) {}
89 
close()90     void close() override {
91         if (isOpen())
92         {
93             m_oldFileName = fileName();
94         }
95         QTemporaryFile::close();
96     }
97 
openNonInheritable()98     bool openNonInheritable()
99     {
100         if (!QTemporaryFile::open()) {
101             return false;
102         }
103 #if defined(Q_OS_WIN)
104         //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable.
105         //The handle is then inherited by gpgsm, which prevents deletion of the temp file
106         //in FileOutput::doFinalize()
107         //There are no inheritable handles under wince
108         return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0);
109 #endif
110         return true;
111     }
112 
oldFileName() const113     QString oldFileName() const
114     {
115         return m_oldFileName;
116     }
117 
118 private:
119     QString m_oldFileName;
120 };
121 
122 template <typename T_IODevice>
123 struct inhibit_close : T_IODevice {
inhibit_close__anon472f3bd00111::inhibit_close124     explicit inhibit_close() : T_IODevice() {}
125     template <typename T1>
inhibit_close__anon472f3bd00111::inhibit_close126     explicit inhibit_close(T1 &t1) : T_IODevice(t1) {}
127 
close__anon472f3bd00111::inhibit_close128     /* reimp */ void close() override {}
reallyClose__anon472f3bd00111::inhibit_close129     void reallyClose()
130     {
131         T_IODevice::close();
132     }
133 };
134 
135 template <typename T_IODevice>
136 struct redirect_close : T_IODevice {
redirect_close__anon472f3bd00111::redirect_close137     explicit redirect_close() : T_IODevice(), m_closed(false) {}
138     template <typename T1>
redirect_close__anon472f3bd00111::redirect_close139     explicit redirect_close(T1 &t1) : T_IODevice(t1), m_closed(false) {}
140 
close__anon472f3bd00111::redirect_close141     /* reimp */ void close() override
142     {
143         this->closeWriteChannel();
144         m_closed = true;
145     }
146 
isClosed__anon472f3bd00111::redirect_close147     bool isClosed() const
148     {
149         return m_closed;
150     }
151 private:
152     bool m_closed;
153 };
154 
155 class FileOutput;
156 class OutputInput : public InputImplBase
157 {
158 public:
159     OutputInput(const std::shared_ptr<FileOutput> &output);
160 
classification() const161     unsigned int classification() const override
162     {
163         return 0;
164     }
165 
outputFinalized()166     void outputFinalized()
167     {
168         if (!m_ioDevice->open(QIODevice::ReadOnly)) {
169             qCCritical(KLEOPATRA_LOG) << "Failed to open file for reading";
170         }
171     }
172 
ioDevice() const173     std::shared_ptr<QIODevice> ioDevice() const override
174     {
175         return m_ioDevice;
176     }
177 
size() const178     unsigned long long size() const override
179     {
180         return 0;
181     }
182 
183 private:
184     std::shared_ptr<FileOutput> m_output;
185     mutable std::shared_ptr<QIODevice> m_ioDevice = nullptr;
186 };
187 
188 class OutputImplBase : public Output
189 {
190 public:
OutputImplBase()191     OutputImplBase()
192         : Output(),
193           m_defaultLabel(),
194           m_customLabel(),
195           m_errorString(),
196           m_isFinalized(false),
197           m_isFinalizing(false),
198           m_cancelPending(false),
199           m_canceled(false),
200           m_binaryOpt(false)
201     {
202 
203     }
204 
label() const205     QString label() const override
206     {
207         return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel;
208     }
setLabel(const QString & label)209     void setLabel(const QString &label) override {
210         m_customLabel = label;
211     }
setDefaultLabel(const QString & l)212     void setDefaultLabel(const QString &l)
213     {
214         m_defaultLabel = l;
215     }
setBinaryOpt(bool value)216     void setBinaryOpt(bool value) override {
217         m_binaryOpt = value;
218     }
binaryOpt() const219     bool binaryOpt() const override
220     {
221         return m_binaryOpt;
222     }
223 
errorString() const224     QString errorString() const override
225     {
226         if (m_errorString.dirty()) {
227             m_errorString = doErrorString();
228         }
229         return m_errorString;
230     }
231 
isFinalized() const232     bool isFinalized() const override
233     {
234         return m_isFinalized;
235     }
finalize()236     void finalize() override {
237         qCDebug(KLEOPATRA_LOG) << this;
238         if (m_isFinalized || m_isFinalizing)
239         {
240             return;
241         }
242         m_isFinalizing = true;
243         try {
244             doFinalize();
245         } catch (...)
246         {
247             m_isFinalizing = false;
248             throw;
249         }
250         m_isFinalizing = false;
251         m_isFinalized = true;
252         if (m_cancelPending)
253         {
254             cancel();
255         }
256     }
257 
cancel()258     void cancel() override {
259         qCDebug(KLEOPATRA_LOG) << this;
260         if (m_isFinalizing)
261         {
262             m_cancelPending = true;
263         } else if (!m_canceled)
264         {
265             m_isFinalizing = true;
266             try {
267                 doCancel();
268             } catch (...) {}
269             m_isFinalizing = false;
270             m_isFinalized = false;
271             m_canceled = true;
272         }
273     }
274 private:
doErrorString() const275     virtual QString doErrorString() const
276     {
277         if (std::shared_ptr<QIODevice> io = ioDevice()) {
278             return io->errorString();
279         } else {
280             return i18n("No output device");
281         }
282     }
283     virtual void doFinalize() = 0;
284     virtual void doCancel() = 0;
285 private:
286     QString m_defaultLabel;
287     QString m_customLabel;
288     mutable cached<QString> m_errorString;
289     bool m_isFinalized   : 1;
290     bool m_isFinalizing  : 1;
291     bool m_cancelPending : 1;
292     bool m_canceled      : 1;
293     bool m_binaryOpt     : 1;
294 };
295 
296 class PipeOutput : public OutputImplBase
297 {
298 public:
299     explicit PipeOutput(assuan_fd_t fd);
300 
ioDevice() const301     std::shared_ptr<QIODevice> ioDevice() const override
302     {
303         return m_io;
304     }
doFinalize()305     void doFinalize() override {
306         m_io->reallyClose();
307     }
doCancel()308     void doCancel() override {
309         doFinalize();
310     }
311 private:
312     std::shared_ptr< inhibit_close<KDPipeIODevice> > m_io;
313 };
314 
315 class ProcessStdInOutput : public OutputImplBase
316 {
317 public:
318     explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd);
319 
ioDevice() const320     std::shared_ptr<QIODevice> ioDevice() const override
321     {
322         return m_proc;
323     }
doFinalize()324     void doFinalize() override {
325         /*
326           Make sure the data is written in the output here. If this
327           is not done the output will be written in small chunks
328           trough the eventloop causing an unnecessary delay before
329           the process has even a chance to work and finish.
330           This delay is mainly noticeable on Windows where it can
331           take ~30 seconds to write out a 10MB file in the 512 byte
332           chunks gpgme serves. */
333         qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite()
334         << " Bytes to be written";
335         while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT));
336 
337         if (!m_proc->isClosed())
338         {
339             m_proc->close();
340         }
341         m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT);
342     }
343 
failed() const344     bool failed() const override
345     {
346         if (!m_proc) {
347             return false;
348         }
349         return !(m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0);
350     }
351 
doCancel()352     void doCancel() override {
353         m_proc->terminate();
354         QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill);
355     }
356     QString label() const override;
357 
358 private:
359     QString doErrorString() const override;
360 
361 private:
362     const QString m_command;
363     const QStringList m_arguments;
364     const std::shared_ptr< redirect_close<QProcess> > m_proc;
365 };
366 
367 class FileOutput : public OutputImplBase
368 {
369 public:
370     explicit FileOutput(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy);
~FileOutput()371     ~FileOutput() override
372     {
373         qCDebug(KLEOPATRA_LOG) << this;
374     }
375 
label() const376     QString label() const override
377     {
378         return QFileInfo(m_fileName).fileName();
379     }
ioDevice() const380     std::shared_ptr<QIODevice> ioDevice() const override
381     {
382         return m_tmpFile;
383     }
384     void doFinalize() override;
doCancel()385     void doCancel() override {
386         qCDebug(KLEOPATRA_LOG) << this;
387     }
fileName() const388     QString fileName() const
389     {
390         return m_fileName;
391     }
392 
attachInput(const std::shared_ptr<OutputInput> & input)393     void attachInput(const std::shared_ptr<OutputInput> &input)
394     {
395         m_attachedInput = std::weak_ptr<OutputInput>(input);
396     }
397 
398 private:
399     bool obtainOverwritePermission();
400 
401 private:
402     const QString m_fileName;
403     std::shared_ptr< TemporaryFile > m_tmpFile;
404     const std::shared_ptr<OverwritePolicy> m_policy;
405     std::weak_ptr<OutputInput> m_attachedInput;
406 };
407 
408 #ifndef QT_NO_CLIPBOARD
409 class ClipboardOutput : public OutputImplBase
410 {
411 public:
412     explicit ClipboardOutput(QClipboard::Mode mode);
413 
414     QString label() const override;
ioDevice() const415     std::shared_ptr<QIODevice> ioDevice() const override
416     {
417         return m_buffer;
418     }
419     void doFinalize() override;
doCancel()420     void doCancel() override {}
421 
422 private:
doErrorString() const423     QString doErrorString() const override
424     {
425         return QString();
426     }
427 private:
428     const QClipboard::Mode m_mode;
429     std::shared_ptr<QBuffer> m_buffer;
430 };
431 #endif // QT_NO_CLIPBOARD
432 
433 class ByteArrayOutput: public OutputImplBase
434 {
435 public:
ByteArrayOutput(QByteArray * data)436     explicit ByteArrayOutput(QByteArray *data):
437         m_buffer(std::shared_ptr<QBuffer>(new QBuffer(data)))
438     {
439         if (!m_buffer->open(QIODevice::WriteOnly))
440             throw Exception(gpg_error(GPG_ERR_EIO),
441                             QStringLiteral("Could not open bytearray for writing?!"));
442     }
443 
label() const444     QString label() const override
445     {
446         return m_label;
447     }
448 
setLabel(const QString & label)449     void setLabel(const QString &label) override
450     {
451         m_label = label;
452     }
453 
ioDevice() const454     std::shared_ptr<QIODevice> ioDevice() const override
455     {
456         return m_buffer;
457     }
458 
doFinalize()459     void doFinalize() override
460     {
461         m_buffer->close();
462     }
463 
doCancel()464     void doCancel() override
465     {
466         m_buffer->close();
467     }
468 
469 private:
doErrorString() const470     QString doErrorString() const override
471     {
472         return QString();
473     }
474 private:
475     QString m_label;
476     std::shared_ptr<QBuffer> m_buffer;
477 };
478 
479 }
480 
createFromPipeDevice(assuan_fd_t fd,const QString & label)481 std::shared_ptr<Output> Output::createFromPipeDevice(assuan_fd_t fd, const QString &label)
482 {
483     std::shared_ptr<PipeOutput> po(new PipeOutput(fd));
484     po->setDefaultLabel(label);
485     return po;
486 }
487 
PipeOutput(assuan_fd_t fd)488 PipeOutput::PipeOutput(assuan_fd_t fd)
489     : OutputImplBase(),
490       m_io(new inhibit_close<KDPipeIODevice>)
491 {
492     errno = 0;
493     if (!m_io->open(fd, QIODevice::WriteOnly))
494         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
495                         i18n("Could not open FD %1 for writing",
496                              assuanFD2int(fd)));
497 }
498 
createFromFile(const QString & fileName,bool forceOverwrite)499 std::shared_ptr<Output> Output::createFromFile(const QString &fileName, bool forceOverwrite)
500 {
501     return createFromFile(fileName, std::shared_ptr<OverwritePolicy>(new OverwritePolicy(nullptr, forceOverwrite ? OverwritePolicy::Allow : OverwritePolicy::Deny)));
502 
503 }
createFromFile(const QString & fileName,const std::shared_ptr<OverwritePolicy> & policy)504 std::shared_ptr<Output> Output::createFromFile(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy)
505 {
506     std::shared_ptr<FileOutput> fo(new FileOutput(fileName, policy));
507     qCDebug(KLEOPATRA_LOG) << fo.get();
508     return fo;
509 }
510 
FileOutput(const QString & fileName,const std::shared_ptr<OverwritePolicy> & policy)511 FileOutput::FileOutput(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy)
512     : OutputImplBase(),
513       m_fileName(fileName),
514       m_tmpFile(new TemporaryFile(fileName)),
515       m_policy(policy)
516 {
517     Q_ASSERT(m_policy);
518     errno = 0;
519     if (!m_tmpFile->openNonInheritable())
520         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
521                         i18n("Could not create temporary file for output \"%1\"", fileName));
522 }
523 
obtainOverwritePermission()524 bool FileOutput::obtainOverwritePermission()
525 {
526     if (m_policy->policy() != OverwritePolicy::Ask) {
527         return m_policy->policy() == OverwritePolicy::Allow;
528     }
529     const int sel = KMessageBox::questionYesNoCancel(m_policy->parentWidget(), i18n("The file <b>%1</b> already exists.\n"
530                     "Overwrite?", m_fileName),
531                     i18n("Overwrite Existing File?"),
532                     KStandardGuiItem::overwrite(),
533                     KGuiItem(i18n("Overwrite All")),
534                     KStandardGuiItem::cancel());
535     if (sel == KMessageBox::No) { //Overwrite All
536         m_policy->setPolicy(OverwritePolicy::Allow);
537     }
538     return sel == KMessageBox::Yes || sel == KMessageBox::No;
539 }
540 
doFinalize()541 void FileOutput::doFinalize()
542 {
543     qCDebug(KLEOPATRA_LOG) << this;
544 
545     struct Remover {
546         QString file;
547         ~Remover()
548         {
549             if (QFile::exists(file)) {
550                 QFile::remove(file);
551             }
552         }
553     } remover;
554 
555     kleo_assert(m_tmpFile);
556 
557     if (m_tmpFile->isOpen()) {
558         m_tmpFile->close();
559     }
560 
561     QString tmpFileName = remover.file = m_tmpFile->oldFileName();
562 
563     m_tmpFile->setAutoRemove(false);
564     QPointer<QObject> guard = m_tmpFile.get();
565     m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/
566     kleo_assert(!guard);   // if this triggers, we need to audit for holder of std::shared_ptr<QIODevice>s.
567 
568     const QFileInfo fi(tmpFileName);
569     bool qtbug83365_workaround = false;
570     if (!fi.exists()) {
571         /* QT Bug 83365 since qt 5.13 causes the filename of temporary files
572          * in UNC path name directories (unmounted samba shares) to start with
573          * UNC/ instead of the // that Qt can actually handle for things like
574          * rename and remove. So if we can't find our temporary file we try
575          * to workaround that bug. */
576         qCDebug(KLEOPATRA_LOG) << "failure to find " << tmpFileName;
577         if (tmpFileName.startsWith(QStringLiteral("UNC"))) {
578             tmpFileName.replace(QRegExp(QStringLiteral("^UNC")), QStringLiteral("/"));
579             qtbug83365_workaround = true;
580         }
581         const QFileInfo fi2(tmpFileName);
582         if (!fi2.exists()) {
583             throw Exception(gpg_error(GPG_ERR_EIO),
584                     QStringLiteral("Could not find temporary file \"%1\".").arg(tmpFileName));
585         }
586     }
587     qCDebug(KLEOPATRA_LOG) << this << " renaming " << tmpFileName << "->" << m_fileName;
588 
589     if (QFile::rename(tmpFileName, m_fileName)) {
590         qCDebug(KLEOPATRA_LOG) << this << "succeeded";
591 
592         if (!m_attachedInput.expired()) {
593             m_attachedInput.lock()->outputFinalized();
594         }
595         return;
596     }
597 
598     qCDebug(KLEOPATRA_LOG) << this << "failed";
599 
600     if (!obtainOverwritePermission())
601         throw Exception(gpg_error(GPG_ERR_CANCELED),
602                         i18n("Overwriting declined"));
603 
604     qCDebug(KLEOPATRA_LOG) << this << "going to overwrite" << m_fileName;
605 
606     if (!QFile::remove(m_fileName))
607         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
608                         i18n("Could not remove file \"%1\" for overwriting.", m_fileName));
609 
610     qCDebug(KLEOPATRA_LOG) << this << "succeeded, renaming " << tmpFileName << "->" << m_fileName;
611 
612     if (QFile::rename(tmpFileName, m_fileName)) {
613         qCDebug(KLEOPATRA_LOG) << this << "succeeded";
614 
615         if (!m_attachedInput.expired()) {
616             m_attachedInput.lock()->outputFinalized();
617         }
618         if (qtbug83365_workaround) {
619             QFile::remove(tmpFileName);
620         }
621         return;
622     }
623 
624     qCDebug(KLEOPATRA_LOG) << this << "failed";
625 
626     throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
627                     i18n(R"(Could not rename file "%1" to "%2")",
628                          tmpFileName, m_fileName));
629 }
630 
createFromProcessStdIn(const QString & command)631 std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command)
632 {
633     return std::shared_ptr<Output>(new ProcessStdInOutput(command, QStringList(), QDir::current()));
634 }
635 
createFromProcessStdIn(const QString & command,const QStringList & args)636 std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args)
637 {
638     return std::shared_ptr<Output>(new ProcessStdInOutput(command, args, QDir::current()));
639 }
640 
createFromProcessStdIn(const QString & command,const QStringList & args,const QDir & wd)641 std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd)
642 {
643     return std::shared_ptr<Output>(new ProcessStdInOutput(command, args, wd));
644 }
645 
ProcessStdInOutput(const QString & cmd,const QStringList & args,const QDir & wd)646 ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd)
647     : OutputImplBase(),
648       m_command(cmd),
649       m_arguments(args),
650       m_proc(new redirect_close<QProcess>)
651 {
652     qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << '\n' << cmd << args;
653     if (cmd.isEmpty())
654         throw Exception(gpg_error(GPG_ERR_INV_ARG),
655                         i18n("Command not specified"));
656     m_proc->setWorkingDirectory(wd.absolutePath());
657     m_proc->start(cmd, args);
658     m_proc->setReadChannel(QProcess::StandardError);
659     if (!m_proc->waitForStarted())
660         throw Exception(gpg_error(GPG_ERR_EIO),
661                         i18n("Could not start %1 process: %2", cmd, m_proc->errorString()));
662 }
663 
label() const664 QString ProcessStdInOutput::label() const
665 {
666     if (!m_proc) {
667         return OutputImplBase::label();
668     }
669     // output max. 3 arguments
670     const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' '));
671     if (m_arguments.size() > 3) {
672         return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline);
673     } else {
674         return i18nc("e.g. \"Input to tar xf - file\"",      "Input to %1",     cmdline);
675     }
676 }
677 
doErrorString() const678 QString ProcessStdInOutput::doErrorString() const
679 {
680     kleo_assert(m_proc);
681     if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) {
682         return QString();
683     }
684     if (m_proc->error() == QProcess::UnknownError)
685         return i18n("Error while running %1: %2", m_command,
686                     QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData()));
687     else {
688         return i18n("Failed to execute %1: %2", m_command, m_proc->errorString());
689     }
690 }
691 
692 #ifndef QT_NO_CLIPBOARD
createFromClipboard()693 std::shared_ptr<Output> Output::createFromClipboard()
694 {
695     return std::shared_ptr<Output>(new ClipboardOutput(QClipboard::Clipboard));
696 }
697 
ClipboardOutput(QClipboard::Mode mode)698 ClipboardOutput::ClipboardOutput(QClipboard::Mode mode)
699     : OutputImplBase(),
700       m_mode(mode),
701       m_buffer(new QBuffer)
702 {
703     errno = 0;
704     if (!m_buffer->open(QIODevice::WriteOnly))
705         throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
706                         i18n("Could not write to clipboard"));
707 }
708 
label() const709 QString ClipboardOutput::label() const
710 {
711     switch (m_mode) {
712     case QClipboard::Clipboard:
713         return i18n("Clipboard");
714     case QClipboard::FindBuffer:
715         return i18n("Find buffer");
716     case QClipboard::Selection:
717         return i18n("Selection");
718     }
719     return QString();
720 }
721 
doFinalize()722 void ClipboardOutput::doFinalize()
723 {
724     if (m_buffer->isOpen()) {
725         m_buffer->close();
726     }
727     if (QClipboard *const cb = QApplication::clipboard()) {
728         cb->setText(QString::fromUtf8(m_buffer->data()));
729     } else
730         throw Exception(gpg_error(GPG_ERR_EIO),
731                         i18n("Could not find clipboard"));
732 }
733 #endif // QT_NO_CLIPBOARD
734 
~Output()735 Output::~Output() {}
736 
737 
OutputInput(const std::shared_ptr<FileOutput> & output)738 OutputInput::OutputInput(const std::shared_ptr<FileOutput> &output)
739     : m_output(output)
740     , m_ioDevice(new QFile(output->fileName()))
741 {
742 }
743 
744 
745 
createFromOutput(const std::shared_ptr<Output> & output)746 std::shared_ptr<Input> Input::createFromOutput(const std::shared_ptr<Output> &output)
747 {
748     if (auto fo = std::dynamic_pointer_cast<FileOutput>(output)) {
749         auto input = std::shared_ptr<OutputInput>(new OutputInput(fo));
750         fo->attachInput(input);
751         return input;
752     } else {
753         return {};
754     }
755 }
756 
createFromByteArray(QByteArray * data,const QString & label)757 std::shared_ptr<Output> Output::createFromByteArray(QByteArray *data, const QString &label)
758 {
759     auto ret = std::shared_ptr<ByteArrayOutput>(new ByteArrayOutput(data));
760     ret->setLabel(label);
761     return ret;
762 }
763