output.cpp 20.2 KB
Newer Older
1
/* -*- mode: c++; c-basic-offset:4 -*-
Marc Mutz's avatar
Marc Mutz committed
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
11
#include <config-kleopatra.h>

12
#include "output.h"
13
#include "input_p.h"
Marc Mutz's avatar
Marc Mutz committed
14
15
16
17
#include "detail_p.h"
#include "kleo_assert.h"
#include "kdpipeiodevice.h"
#include "log.h"
Allen Winter's avatar
Allen Winter committed
18
#include "cached.h"
19

20
#include <Libkleo/KleoException>
21

Laurent Montel's avatar
Laurent Montel committed
22
#include <KLocalizedString>
23
#include <KMessageBox>
Laurent Montel's avatar
Laurent Montel committed
24
#include "kleopatra_debug.h"
25

26
#include <QFileInfo>
27
28
#include <QTemporaryFile>
#include <QString>
Marc Mutz's avatar
Marc Mutz committed
29
30
31
#include <QClipboard>
#include <QApplication>
#include <QBuffer>
32
#include <QPointer>
33
#include <QWidget>
Thomas McGuire's avatar
Thomas McGuire committed
34
35
36
#include <QDir>
#include <QProcess>
#include <QTimer>
37

Frank Osterfeld's avatar
Frank Osterfeld committed
38
39
40
41
#ifdef Q_OS_WIN
# include <windows.h>
#endif

42
43
44
#include <errno.h>

using namespace Kleo;
45
using namespace Kleo::_detail;
46

Thomas McGuire's avatar
Thomas McGuire committed
47
static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1;     // no timeout
Laurent Montel's avatar
Laurent Montel committed
48
static const int PROCESS_TERMINATE_TIMEOUT   = 5 * 1000; // 5s
Thomas McGuire's avatar
Thomas McGuire committed
49

Laurent Montel's avatar
Laurent Montel committed
50
51
class OverwritePolicy::Private
{
52
public:
Laurent Montel's avatar
Laurent Montel committed
53
    Private(QWidget *p, OverwritePolicy::Policy pol) : policy(pol), widget(p) {}
54
    OverwritePolicy::Policy policy;
Laurent Montel's avatar
Laurent Montel committed
55
    QWidget *widget;
56
57
};

Laurent Montel's avatar
Laurent Montel committed
58
59
OverwritePolicy::OverwritePolicy(QWidget *parent, Policy initialPolicy) : d(new Private(parent, initialPolicy))
{
60
61
62
63
}

OverwritePolicy::~OverwritePolicy() {}

Laurent Montel's avatar
Laurent Montel committed
64
65
OverwritePolicy::Policy OverwritePolicy::policy() const
{
66
67
68
    return d->policy;
}

Laurent Montel's avatar
Laurent Montel committed
69
70
void OverwritePolicy::setPolicy(Policy policy)
{
71
72
73
    d->policy = policy;
}

Laurent Montel's avatar
Laurent Montel committed
74
75
QWidget *OverwritePolicy::parentWidget() const
{
76
77
78
    return d->widget;
}

Laurent Montel's avatar
Laurent Montel committed
79
80
namespace
{
81

Laurent Montel's avatar
Laurent Montel committed
82
83
84
85
86
87
88
89
class TemporaryFile : public QTemporaryFile
{
public:
    explicit TemporaryFile() : QTemporaryFile() {}
    explicit TemporaryFile(const QString &templateName) : QTemporaryFile(templateName) {}
    explicit TemporaryFile(QObject *parent) : QTemporaryFile(parent) {}
    explicit TemporaryFile(const QString &templateName, QObject *parent) : QTemporaryFile(templateName, parent) {}

90
    void close() override {
Laurent Montel's avatar
Laurent Montel committed
91
92
        if (isOpen())
        {
Laurent Montel's avatar
Laurent Montel committed
93
            m_oldFileName = fileName();
94
        }
Laurent Montel's avatar
Laurent Montel committed
95
96
        QTemporaryFile::close();
    }
97

Laurent Montel's avatar
Laurent Montel committed
98
99
100
101
102
    bool openNonInheritable()
    {
        if (!QTemporaryFile::open()) {
            return false;
        }
103
#if defined(Q_OS_WIN)
Laurent Montel's avatar
Laurent Montel committed
104
105
106
107
108
        //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable.
        //The handle is then inherited by gpgsm, which prevents deletion of the temp file
        //in FileOutput::doFinalize()
        //There are no inheritable handles under wince
        return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0);
109
#endif
Laurent Montel's avatar
Laurent Montel committed
110
111
        return true;
    }
112

Laurent Montel's avatar
Laurent Montel committed
113
114
115
116
    QString oldFileName() const
    {
        return m_oldFileName;
    }
117

Laurent Montel's avatar
Laurent Montel committed
118
119
120
private:
    QString m_oldFileName;
};
121

Laurent Montel's avatar
Laurent Montel committed
122
123
124
125
126
template <typename T_IODevice>
struct inhibit_close : T_IODevice {
    explicit inhibit_close() : T_IODevice() {}
    template <typename T1>
    explicit inhibit_close(T1 &t1) : T_IODevice(t1) {}
127

128
    /* reimp */ void close() override {}
Laurent Montel's avatar
Laurent Montel committed
129
130
131
132
133
    void reallyClose()
    {
        T_IODevice::close();
    }
};
Thomas McGuire's avatar
Thomas McGuire committed
134

Laurent Montel's avatar
Laurent Montel committed
135
136
137
138
139
140
template <typename T_IODevice>
struct redirect_close : T_IODevice {
    explicit redirect_close() : T_IODevice(), m_closed(false) {}
    template <typename T1>
    explicit redirect_close(T1 &t1) : T_IODevice(t1), m_closed(false) {}

141
    /* reimp */ void close() override
Laurent Montel's avatar
Laurent Montel committed
142
143
144
145
146
147
148
149
150
151
152
153
154
    {
        this->closeWriteChannel();
        m_closed = true;
    }

    bool isClosed() const
    {
        return m_closed;
    }
private:
    bool m_closed;
};

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
class FileOutput;
class OutputInput : public InputImplBase
{
public:
    OutputInput(const std::shared_ptr<FileOutput> &output);

    unsigned int classification() const override
    {
        return 0;
    }

    void outputFinalized()
    {
        if (!m_ioDevice->open(QIODevice::ReadOnly)) {
            qCCritical(KLEOPATRA_LOG) << "Failed to open file for reading";
        }
    }

    std::shared_ptr<QIODevice> ioDevice() const override
    {
        return m_ioDevice;
    }

    unsigned long long size() const override
    {
        return 0;
    }

private:
    std::shared_ptr<FileOutput> m_output;
    mutable std::shared_ptr<QIODevice> m_ioDevice = nullptr;
};

Laurent Montel's avatar
Laurent Montel committed
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
class OutputImplBase : public Output
{
public:
    OutputImplBase()
        : Output(),
          m_defaultLabel(),
          m_customLabel(),
          m_errorString(),
          m_isFinalized(false),
          m_isFinalizing(false),
          m_cancelPending(false),
          m_canceled(false),
          m_binaryOpt(false)
    {

    }

205
    QString label() const override
Laurent Montel's avatar
Laurent Montel committed
206
207
208
    {
        return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel;
    }
209
    void setLabel(const QString &label) override {
Laurent Montel's avatar
Laurent Montel committed
210
211
212
213
214
215
        m_customLabel = label;
    }
    void setDefaultLabel(const QString &l)
    {
        m_defaultLabel = l;
    }
216
    void setBinaryOpt(bool value) override {
Laurent Montel's avatar
Laurent Montel committed
217
218
        m_binaryOpt = value;
    }
219
    bool binaryOpt() const override
Laurent Montel's avatar
Laurent Montel committed
220
221
222
223
    {
        return m_binaryOpt;
    }

224
    QString errorString() const override
Laurent Montel's avatar
Laurent Montel committed
225
226
227
    {
        if (m_errorString.dirty()) {
            m_errorString = doErrorString();
228
        }
Laurent Montel's avatar
Laurent Montel committed
229
230
        return m_errorString;
    }
231

232
    bool isFinalized() const override
Laurent Montel's avatar
Laurent Montel committed
233
234
235
    {
        return m_isFinalized;
    }
236
    void finalize() override {
Laurent Montel's avatar
Laurent Montel committed
237
        qCDebug(KLEOPATRA_LOG) << this;
Laurent Montel's avatar
Laurent Montel committed
238
239
        if (m_isFinalized || m_isFinalizing)
        {
Laurent Montel's avatar
Laurent Montel committed
240
            return;
241
        }
Laurent Montel's avatar
Laurent Montel committed
242
243
244
        m_isFinalizing = true;
        try {
            doFinalize();
Laurent Montel's avatar
Laurent Montel committed
245
246
        } catch (...)
        {
Laurent Montel's avatar
Laurent Montel committed
247
248
            m_isFinalizing = false;
            throw;
Allen Winter's avatar
Allen Winter committed
249
        }
Laurent Montel's avatar
Laurent Montel committed
250
251
        m_isFinalizing = false;
        m_isFinalized = true;
Laurent Montel's avatar
Laurent Montel committed
252
253
        if (m_cancelPending)
        {
Laurent Montel's avatar
Laurent Montel committed
254
255
256
257
            cancel();
        }
    }

258
    void cancel() override {
Laurent Montel's avatar
Laurent Montel committed
259
        qCDebug(KLEOPATRA_LOG) << this;
Laurent Montel's avatar
Laurent Montel committed
260
261
        if (m_isFinalizing)
        {
Laurent Montel's avatar
Laurent Montel committed
262
            m_cancelPending = true;
Laurent Montel's avatar
Laurent Montel committed
263
264
        } else if (!m_canceled)
        {
Laurent Montel's avatar
Laurent Montel committed
265
266
267
268
269
            m_isFinalizing = true;
            try {
                doCancel();
            } catch (...) {}
            m_isFinalizing = false;
270
            m_isFinalized = false;
Laurent Montel's avatar
Laurent Montel committed
271
            m_canceled = true;
Thomas McGuire's avatar
Thomas McGuire committed
272
        }
Laurent Montel's avatar
Laurent Montel committed
273
274
275
276
    }
private:
    virtual QString doErrorString() const
    {
277
        if (std::shared_ptr<QIODevice> io = ioDevice()) {
Laurent Montel's avatar
Laurent Montel committed
278
279
280
            return io->errorString();
        } else {
            return i18n("No output device");
Thomas McGuire's avatar
Thomas McGuire committed
281
        }
Laurent Montel's avatar
Laurent Montel committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    }
    virtual void doFinalize() = 0;
    virtual void doCancel() = 0;
private:
    QString m_defaultLabel;
    QString m_customLabel;
    mutable cached<QString> m_errorString;
    bool m_isFinalized   : 1;
    bool m_isFinalizing  : 1;
    bool m_cancelPending : 1;
    bool m_canceled      : 1;
    bool m_binaryOpt     : 1;
};

class PipeOutput : public OutputImplBase
{
public:
    explicit PipeOutput(assuan_fd_t fd);
Thomas McGuire's avatar
Thomas McGuire committed
300

301
    std::shared_ptr<QIODevice> ioDevice() const override
Laurent Montel's avatar
Laurent Montel committed
302
303
304
    {
        return m_io;
    }
305
    void doFinalize() override {
Laurent Montel's avatar
Laurent Montel committed
306
307
        m_io->reallyClose();
    }
308
    void doCancel() override {
Laurent Montel's avatar
Laurent Montel committed
309
310
311
        doFinalize();
    }
private:
312
    std::shared_ptr< inhibit_close<KDPipeIODevice> > m_io;
Laurent Montel's avatar
Laurent Montel committed
313
};
Thomas McGuire's avatar
Thomas McGuire committed
314

Laurent Montel's avatar
Laurent Montel committed
315
316
317
318
class ProcessStdInOutput : public OutputImplBase
{
public:
    explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd);
Thomas McGuire's avatar
Thomas McGuire committed
319

320
    std::shared_ptr<QIODevice> ioDevice() const override
Laurent Montel's avatar
Laurent Montel committed
321
322
323
    {
        return m_proc;
    }
324
    void doFinalize() override {
Laurent Montel's avatar
Laurent Montel committed
325
326
327
        /*
          Make sure the data is written in the output here. If this
          is not done the output will be written in small chunks
328
          trough the eventloop causing an unnecessary delay before
Laurent Montel's avatar
Laurent Montel committed
329
          the process has even a chance to work and finish.
330
          This delay is mainly noticeable on Windows where it can
Laurent Montel's avatar
Laurent Montel committed
331
332
          take ~30 seconds to write out a 10MB file in the 512 byte
          chunks gpgme serves. */
Laurent Montel's avatar
Laurent Montel committed
333
        qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite()
Laurent Montel's avatar
Laurent Montel committed
334
        << " Bytes to be written";
Laurent Montel's avatar
Laurent Montel committed
335
336
        while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT));

Laurent Montel's avatar
Laurent Montel committed
337
338
        if (!m_proc->isClosed())
        {
Laurent Montel's avatar
Laurent Montel committed
339
340
341
342
            m_proc->close();
        }
        m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT);
    }
343
344
345
346
347
348
349
350
351

    bool failed() const override
    {
        if (!m_proc) {
            return false;
        }
        return !(m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0);
    }

352
    void doCancel() override {
Laurent Montel's avatar
Laurent Montel committed
353
        m_proc->terminate();
354
        QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill);
Laurent Montel's avatar
Laurent Montel committed
355
    }
356
    QString label() const override;
Thomas McGuire's avatar
Thomas McGuire committed
357

Laurent Montel's avatar
Laurent Montel committed
358
private:
359
    QString doErrorString() const override;
360

Laurent Montel's avatar
Laurent Montel committed
361
362
363
private:
    const QString m_command;
    const QStringList m_arguments;
364
    const std::shared_ptr< redirect_close<QProcess> > m_proc;
Laurent Montel's avatar
Laurent Montel committed
365
};
366

Laurent Montel's avatar
Laurent Montel committed
367
368
369
class FileOutput : public OutputImplBase
{
public:
370
    explicit FileOutput(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy);
Laurent Montel's avatar
Laurent Montel committed
371
372
    ~FileOutput()
    {
Laurent Montel's avatar
Laurent Montel committed
373
        qCDebug(KLEOPATRA_LOG) << this;
Laurent Montel's avatar
Laurent Montel committed
374
375
    }

376
    QString label() const override
Laurent Montel's avatar
Laurent Montel committed
377
378
379
    {
        return QFileInfo(m_fileName).fileName();
    }
380
    std::shared_ptr<QIODevice> ioDevice() const override
Laurent Montel's avatar
Laurent Montel committed
381
382
383
    {
        return m_tmpFile;
    }
384
385
    void doFinalize() override;
    void doCancel() override {
Laurent Montel's avatar
Laurent Montel committed
386
        qCDebug(KLEOPATRA_LOG) << this;
Laurent Montel's avatar
Laurent Montel committed
387
    }
388
389
390
391
392
393
394
395
396
397
    QString fileName() const
    {
        return m_fileName;
    }

    void attachInput(const std::shared_ptr<OutputInput> &input)
    {
        m_attachedInput = std::weak_ptr<OutputInput>(input);
    }

Laurent Montel's avatar
Laurent Montel committed
398
399
400
401
402
private:
    bool obtainOverwritePermission();

private:
    const QString m_fileName;
403
404
    std::shared_ptr< TemporaryFile > m_tmpFile;
    const std::shared_ptr<OverwritePolicy> m_policy;
405
    std::weak_ptr<OutputInput> m_attachedInput;
Laurent Montel's avatar
Laurent Montel committed
406
};
407

408
#ifndef QT_NO_CLIPBOARD
Laurent Montel's avatar
Laurent Montel committed
409
410
411
412
413
class ClipboardOutput : public OutputImplBase
{
public:
    explicit ClipboardOutput(QClipboard::Mode mode);

414
415
    QString label() const override;
    std::shared_ptr<QIODevice> ioDevice() const override
Laurent Montel's avatar
Laurent Montel committed
416
417
418
    {
        return m_buffer;
    }
419
420
    void doFinalize() override;
    void doCancel() override {}
Laurent Montel's avatar
Laurent Montel committed
421
422

private:
423
    QString doErrorString() const override
Laurent Montel's avatar
Laurent Montel committed
424
425
426
427
428
    {
        return QString();
    }
private:
    const QClipboard::Mode m_mode;
429
    std::shared_ptr<QBuffer> m_buffer;
Laurent Montel's avatar
Laurent Montel committed
430
};
431
#endif // QT_NO_CLIPBOARD
432

433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
class ByteArrayOutput: public OutputImplBase
{
public:
    explicit ByteArrayOutput(QByteArray *data):
        m_buffer(std::shared_ptr<QBuffer>(new QBuffer(data)))
    {
        if (!m_buffer->open(QIODevice::WriteOnly))
            throw Exception(gpg_error(GPG_ERR_EIO),
                            QStringLiteral("Could not open bytearray for writing?!"));
    }

    QString label() const override
    {
        return m_label;
    }

    void setLabel(const QString &label) override
    {
        m_label = label;
    }

    std::shared_ptr<QIODevice> ioDevice() const override
    {
        return m_buffer;
    }

    void doFinalize() override
    {
        m_buffer->close();
    }

    void doCancel() override
    {
        m_buffer->close();
    }

private:
    QString doErrorString() const override
    {
        return QString();
    }
private:
    QString m_label;
    std::shared_ptr<QBuffer> m_buffer;
};

479
480
}

481
std::shared_ptr<Output> Output::createFromPipeDevice(assuan_fd_t fd, const QString &label)
Laurent Montel's avatar
Laurent Montel committed
482
{
483
    std::shared_ptr<PipeOutput> po(new PipeOutput(fd));
Laurent Montel's avatar
Laurent Montel committed
484
    po->setDefaultLabel(label);
485
486
487
    return po;
}

Laurent Montel's avatar
Laurent Montel committed
488
PipeOutput::PipeOutput(assuan_fd_t fd)
489
    : OutputImplBase(),
Laurent Montel's avatar
Laurent Montel committed
490
      m_io(new inhibit_close<KDPipeIODevice>)
491
492
{
    errno = 0;
Laurent Montel's avatar
Laurent Montel committed
493
494
495
496
    if (!m_io->open(fd, QIODevice::WriteOnly))
        throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
                        i18n("Could not open FD %1 for writing",
                             assuanFD2int(fd)));
497
498
}

499
std::shared_ptr<Output> Output::createFromFile(const QString &fileName, bool forceOverwrite)
Laurent Montel's avatar
Laurent Montel committed
500
{
Laurent Montel's avatar
Laurent Montel committed
501
    return createFromFile(fileName, std::shared_ptr<OverwritePolicy>(new OverwritePolicy(nullptr, forceOverwrite ? OverwritePolicy::Allow : OverwritePolicy::Deny)));
502

503
}
504
std::shared_ptr<Output> Output::createFromFile(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy)
Laurent Montel's avatar
Laurent Montel committed
505
{
506
    std::shared_ptr<FileOutput> fo(new FileOutput(fileName, policy));
Laurent Montel's avatar
Laurent Montel committed
507
    qCDebug(KLEOPATRA_LOG) << fo.get();
508
509
510
    return fo;
}

511
FileOutput::FileOutput(const QString &fileName, const std::shared_ptr<OverwritePolicy> &policy)
512
    : OutputImplBase(),
Laurent Montel's avatar
Laurent Montel committed
513
514
515
      m_fileName(fileName),
      m_tmpFile(new TemporaryFile(fileName)),
      m_policy(policy)
516
{
517
    Q_ASSERT(m_policy);
518
    errno = 0;
Laurent Montel's avatar
Laurent Montel committed
519
520
521
    if (!m_tmpFile->openNonInheritable())
        throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
                        i18n("Could not create temporary file for output \"%1\"", fileName));
522
523
}

Laurent Montel's avatar
Laurent Montel committed
524
525
526
bool FileOutput::obtainOverwritePermission()
{
    if (m_policy->policy() != OverwritePolicy::Ask) {
527
        return m_policy->policy() == OverwritePolicy::Allow;
Laurent Montel's avatar
Laurent Montel committed
528
529
530
531
532
533
534
535
536
537
    }
    const int sel = KMessageBox::questionYesNoCancel(m_policy->parentWidget(), i18n("The file <b>%1</b> already exists.\n"
                    "Overwrite?", m_fileName),
                    i18n("Overwrite Existing File?"),
                    KStandardGuiItem::overwrite(),
                    KGuiItem(i18n("Overwrite All")),
                    KStandardGuiItem::cancel());
    if (sel == KMessageBox::No) { //Overwrite All
        m_policy->setPolicy(OverwritePolicy::Allow);
    }
538
    return sel == KMessageBox::Yes || sel == KMessageBox::No;
539
540
}

Laurent Montel's avatar
Laurent Montel committed
541
542
void FileOutput::doFinalize()
{
Laurent Montel's avatar
Laurent Montel committed
543
    qCDebug(KLEOPATRA_LOG) << this;
544

545
546
    struct Remover {
        QString file;
Laurent Montel's avatar
Laurent Montel committed
547
548
549
550
551
552
        ~Remover()
        {
            if (QFile::exists(file)) {
                QFile::remove(file);
            }
        }
553
554
    } remover;

Laurent Montel's avatar
Laurent Montel committed
555
    kleo_assert(m_tmpFile);
556

Laurent Montel's avatar
Laurent Montel committed
557
    if (m_tmpFile->isOpen()) {
558
        m_tmpFile->close();
Laurent Montel's avatar
Laurent Montel committed
559
    }
560

561
    QString tmpFileName = remover.file = m_tmpFile->oldFileName();
562

Laurent Montel's avatar
Laurent Montel committed
563
    m_tmpFile->setAutoRemove(false);
564
565
    QPointer<QObject> guard = m_tmpFile.get();
    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
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
    const QFileInfo fi(tmpFileName);
    bool qtbug83365_workaround = false;
    if (!fi.exists()) {
        /* QT Bug 83365 since qt 5.13 causes the filename of temporary files
         * in UNC path name directories (unmounted samba shares) to start with
         * UNC/ instead of the // that Qt can actually handle for things like
         * rename and remove. So if we can't find our temporary file we try
         * to workaround that bug. */
        qCDebug(KLEOPATRA_LOG) << "failure to find " << tmpFileName;
        if (tmpFileName.startsWith(QStringLiteral("UNC"))) {
            tmpFileName.replace(QRegExp(QStringLiteral("^UNC")), QStringLiteral("/"));
            qtbug83365_workaround = true;
        }
        const QFileInfo fi2(tmpFileName);
        if (!fi2.exists()) {
            throw Exception(gpg_error(GPG_ERR_EIO),
                    QStringLiteral("Could not find temporary file \"%1\".").arg(m_fileName));
        }
    }
Laurent Montel's avatar
Laurent Montel committed
587
    qCDebug(KLEOPATRA_LOG) << this << " renaming " << tmpFileName << "->" << m_fileName;
588

Laurent Montel's avatar
Laurent Montel committed
589
    if (QFile::rename(tmpFileName, m_fileName)) {
Laurent Montel's avatar
Laurent Montel committed
590
        qCDebug(KLEOPATRA_LOG) << this << "succeeded";
591
592
593
594

        if (!m_attachedInput.expired()) {
            m_attachedInput.lock()->outputFinalized();
        }
595
596
597
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
598
    qCDebug(KLEOPATRA_LOG) << this << "failed";
Marc Mutz's avatar
Marc Mutz committed
599

Laurent Montel's avatar
Laurent Montel committed
600
601
602
    if (!obtainOverwritePermission())
        throw Exception(gpg_error(GPG_ERR_CANCELED),
                        i18n("Overwriting declined"));
603

Laurent Montel's avatar
Laurent Montel committed
604
    qCDebug(KLEOPATRA_LOG) << this << "going to overwrite" << m_fileName;
Marc Mutz's avatar
Marc Mutz committed
605

Laurent Montel's avatar
Laurent Montel committed
606
607
608
    if (!QFile::remove(m_fileName))
        throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
                        i18n("Could not remove file \"%1\" for overwriting.", m_fileName));
609

Laurent Montel's avatar
Laurent Montel committed
610
    qCDebug(KLEOPATRA_LOG) << this << "succeeded, renaming " << tmpFileName << "->" << m_fileName;
Marc Mutz's avatar
Marc Mutz committed
611

Laurent Montel's avatar
Laurent Montel committed
612
    if (QFile::rename(tmpFileName, m_fileName)) {
Laurent Montel's avatar
Laurent Montel committed
613
        qCDebug(KLEOPATRA_LOG) << this << "succeeded";
614
615
616
617

        if (!m_attachedInput.expired()) {
            m_attachedInput.lock()->outputFinalized();
        }
618
619
620
        if (qtbug83365_workaround) {
            QFile::remove(tmpFileName);
        }
621
622
623
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
624
    qCDebug(KLEOPATRA_LOG) << this << "failed";
Marc Mutz's avatar
Marc Mutz committed
625

Laurent Montel's avatar
Laurent Montel committed
626
627
628
    throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
                    i18n("Could not rename file \"%1\" to \"%2\"",
                         tmpFileName, m_fileName));
629
630
}

631
std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command)
Laurent Montel's avatar
Laurent Montel committed
632
{
633
    return std::shared_ptr<Output>(new ProcessStdInOutput(command, QStringList(), QDir::current()));
Thomas McGuire's avatar
Thomas McGuire committed
634
635
}

636
std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args)
Laurent Montel's avatar
Laurent Montel committed
637
{
638
    return std::shared_ptr<Output>(new ProcessStdInOutput(command, args, QDir::current()));
Thomas McGuire's avatar
Thomas McGuire committed
639
640
}

641
std::shared_ptr<Output> Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd)
Laurent Montel's avatar
Laurent Montel committed
642
{
643
    return std::shared_ptr<Output>(new ProcessStdInOutput(command, args, wd));
Thomas McGuire's avatar
Thomas McGuire committed
644
645
}

Laurent Montel's avatar
Laurent Montel committed
646
ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd)
Thomas McGuire's avatar
Thomas McGuire committed
647
    : OutputImplBase(),
Laurent Montel's avatar
Laurent Montel committed
648
649
650
      m_command(cmd),
      m_arguments(args),
      m_proc(new redirect_close<QProcess>)
Thomas McGuire's avatar
Thomas McGuire committed
651
{
Laurent Montel's avatar
Laurent Montel committed
652
    qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << '\n' << cmd << args;
Laurent Montel's avatar
Laurent Montel committed
653
654
655
656
657
658
659
660
661
    if (cmd.isEmpty())
        throw Exception(gpg_error(GPG_ERR_INV_ARG),
                        i18n("Command not specified"));
    m_proc->setWorkingDirectory(wd.absolutePath());
    m_proc->start(cmd, args);
    m_proc->setReadChannel(QProcess::StandardError);
    if (!m_proc->waitForStarted())
        throw Exception(gpg_error(GPG_ERR_EIO),
                        i18n("Could not start %1 process: %2", cmd, m_proc->errorString()));
Thomas McGuire's avatar
Thomas McGuire committed
662
663
}

Laurent Montel's avatar
Laurent Montel committed
664
665
666
QString ProcessStdInOutput::label() const
{
    if (!m_proc) {
Thomas McGuire's avatar
Thomas McGuire committed
667
        return OutputImplBase::label();
Laurent Montel's avatar
Laurent Montel committed
668
    }
Thomas McGuire's avatar
Thomas McGuire committed
669
    // output max. 3 arguments
Laurent Montel's avatar
Laurent Montel committed
670
    const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' '));
Laurent Montel's avatar
Laurent Montel committed
671
672
673
674
675
    if (m_arguments.size() > 3) {
        return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline);
    } else {
        return i18nc("e.g. \"Input to tar xf - file\"",      "Input to %1",     cmdline);
    }
Thomas McGuire's avatar
Thomas McGuire committed
676
677
}

Laurent Montel's avatar
Laurent Montel committed
678
679
680
681
QString ProcessStdInOutput::doErrorString() const
{
    kleo_assert(m_proc);
    if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) {
Thomas McGuire's avatar
Thomas McGuire committed
682
        return QString();
Laurent Montel's avatar
Laurent Montel committed
683
684
685
686
687
688
689
    }
    if (m_proc->error() == QProcess::UnknownError)
        return i18n("Error while running %1: %2", m_command,
                    QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData()));
    else {
        return i18n("Failed to execute %1: %2", m_command, m_proc->errorString());
    }
Thomas McGuire's avatar
Thomas McGuire committed
690
691
}

692
#ifndef QT_NO_CLIPBOARD
693
std::shared_ptr<Output> Output::createFromClipboard()
Laurent Montel's avatar
Laurent Montel committed
694
{
695
    return std::shared_ptr<Output>(new ClipboardOutput(QClipboard::Clipboard));
Marc Mutz's avatar
Marc Mutz committed
696
697
}

Laurent Montel's avatar
Laurent Montel committed
698
ClipboardOutput::ClipboardOutput(QClipboard::Mode mode)
Marc Mutz's avatar
Marc Mutz committed
699
    : OutputImplBase(),
Laurent Montel's avatar
Laurent Montel committed
700
701
      m_mode(mode),
      m_buffer(new QBuffer)
Marc Mutz's avatar
Marc Mutz committed
702
703
{
    errno = 0;
Laurent Montel's avatar
Laurent Montel committed
704
705
706
    if (!m_buffer->open(QIODevice::WriteOnly))
        throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO),
                        i18n("Could not write to clipboard"));
Marc Mutz's avatar
Marc Mutz committed
707
708
}

Laurent Montel's avatar
Laurent Montel committed
709
710
711
QString ClipboardOutput::label() const
{
    switch (m_mode) {
Marc Mutz's avatar
Marc Mutz committed
712
    case QClipboard::Clipboard:
Laurent Montel's avatar
Laurent Montel committed
713
        return i18n("Clipboard");
Marc Mutz's avatar
Marc Mutz committed
714
    case QClipboard::FindBuffer:
Laurent Montel's avatar
Laurent Montel committed
715
        return i18n("Find buffer");
Marc Mutz's avatar
Marc Mutz committed
716
    case QClipboard::Selection:
Laurent Montel's avatar
Laurent Montel committed
717
        return i18n("Selection");
Marc Mutz's avatar
Marc Mutz committed
718
719
    }
    return QString();
720
}
Marc Mutz's avatar
Marc Mutz committed
721

Laurent Montel's avatar
Laurent Montel committed
722
723
724
void ClipboardOutput::doFinalize()
{
    if (m_buffer->isOpen()) {
Marc Mutz's avatar
Marc Mutz committed
725
        m_buffer->close();
Laurent Montel's avatar
Laurent Montel committed
726
727
728
729
730
731
    }
    if (QClipboard *const cb = QApplication::clipboard()) {
        cb->setText(QString::fromUtf8(m_buffer->data()));
    } else
        throw Exception(gpg_error(GPG_ERR_EIO),
                        i18n("Could not find clipboard"));
Marc Mutz's avatar
Marc Mutz committed
732
}
733
#endif // QT_NO_CLIPBOARD
Marc Mutz's avatar
Marc Mutz committed
734

735
Output::~Output() {}
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755


OutputInput::OutputInput(const std::shared_ptr<FileOutput> &output)
    : m_output(output)
    , m_ioDevice(new QFile(output->fileName()))
{
}



std::shared_ptr<Input> Input::createFromOutput(const std::shared_ptr<Output> &output)
{
    if (auto fo = std::dynamic_pointer_cast<FileOutput>(output)) {
        auto input = std::shared_ptr<OutputInput>(new OutputInput(fo));
        fo->attachInput(input);
        return input;
    } else {
        return {};
    }
}
756
757
758
759
760
761
762

std::shared_ptr<Output> Output::createFromByteArray(QByteArray *data, const QString &label)
{
    auto ret = std::shared_ptr<ByteArrayOutput>(new ByteArrayOutput(data));
    ret->setLabel(label);
    return ret;
}