1 /* -*- mode: c++; c-basic-offset:4 -*-
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2007 Klarälvdalens Datakonsult AB
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the Qt library by Trolltech AS, Norway (or with modified versions
24 of Qt that use the same license as Qt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 Qt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
33 #include <config-kleopatra.h>
38 #include "kleo_assert.h"
39 #include "kdpipeiodevice.h"
43 #include <Libkleo/Exception>
45 #include <KLocalizedString>
46 #include <KMessageBox>
47 #include "kleopatra_debug.h"
50 #include <QTemporaryFile>
53 #include <QApplication>
68 using namespace Kleo::_detail
;
69 using namespace boost
;
71 static const int PROCESS_MAX_RUNTIME_TIMEOUT
= -1; // no timeout
72 static const int PROCESS_TERMINATE_TIMEOUT
= 5 * 1000; // 5s
74 class OverwritePolicy::Private
77 Private(QWidget
*p
, OverwritePolicy::Policy pol
) : policy(pol
), widget(p
) {}
78 OverwritePolicy::Policy policy
;
82 OverwritePolicy::OverwritePolicy(QWidget
*parent
, Policy initialPolicy
) : d(new Private(parent
, initialPolicy
))
86 OverwritePolicy::~OverwritePolicy() {}
88 OverwritePolicy::Policy
OverwritePolicy::policy() const
93 void OverwritePolicy::setPolicy(Policy policy
)
98 QWidget
*OverwritePolicy::parentWidget() const
106 class TemporaryFile
: public QTemporaryFile
109 explicit TemporaryFile() : QTemporaryFile() {}
110 explicit TemporaryFile(const QString
&templateName
) : QTemporaryFile(templateName
) {}
111 explicit TemporaryFile(QObject
*parent
) : QTemporaryFile(parent
) {}
112 explicit TemporaryFile(const QString
&templateName
, QObject
*parent
) : QTemporaryFile(templateName
, parent
) {}
114 void close() Q_DECL_OVERRIDE
{
117 m_oldFileName
= fileName();
119 QTemporaryFile::close();
122 bool openNonInheritable()
124 if (!QTemporaryFile::open()) {
127 #if defined(Q_OS_WIN)
128 //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable.
129 //The handle is then inherited by gpgsm, which prevents deletion of the temp file
130 //in FileOutput::doFinalize()
131 //There are no inheritable handles under wince
132 return SetHandleInformation((HANDLE
)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT
, 0);
137 QString
oldFileName() const
139 return m_oldFileName
;
143 QString m_oldFileName
;
146 template <typename T_IODevice
>
147 struct inhibit_close
: T_IODevice
{
148 explicit inhibit_close() : T_IODevice() {}
149 template <typename T1
>
150 explicit inhibit_close(T1
&t1
) : T_IODevice(t1
) {}
152 /* reimp */ void close() {}
159 template <typename T_IODevice
>
160 struct redirect_close
: T_IODevice
{
161 explicit redirect_close() : T_IODevice(), m_closed(false) {}
162 template <typename T1
>
163 explicit redirect_close(T1
&t1
) : T_IODevice(t1
), m_closed(false) {}
165 /* reimp */ void close()
167 this->closeWriteChannel();
171 bool isClosed() const
179 class OutputImplBase
: public Output
187 m_isFinalized(false),
188 m_isFinalizing(false),
189 m_cancelPending(false),
196 QString
label() const Q_DECL_OVERRIDE
198 return m_customLabel
.isEmpty() ? m_defaultLabel
: m_customLabel
;
200 void setLabel(const QString
&label
) Q_DECL_OVERRIDE
{
201 m_customLabel
= label
;
203 void setDefaultLabel(const QString
&l
)
207 void setBinaryOpt(bool value
) Q_DECL_OVERRIDE
{
210 bool binaryOpt() const Q_DECL_OVERRIDE
215 QString
errorString() const Q_DECL_OVERRIDE
217 if (m_errorString
.dirty()) {
218 m_errorString
= doErrorString();
220 return m_errorString
;
223 bool isFinalized() const Q_DECL_OVERRIDE
225 return m_isFinalized
;
227 void finalize() Q_DECL_OVERRIDE
{
228 qCDebug(KLEOPATRA_LOG
) << this;
229 if (m_isFinalized
|| m_isFinalizing
)
233 m_isFinalizing
= true;
238 m_isFinalizing
= false;
241 m_isFinalizing
= false;
242 m_isFinalized
= true;
249 void cancel() Q_DECL_OVERRIDE
{
250 qCDebug(KLEOPATRA_LOG
) << this;
253 m_cancelPending
= true;
254 } else if (!m_canceled
)
256 m_isFinalizing
= true;
260 m_isFinalizing
= false;
261 m_isFinalized
= true;
266 virtual QString
doErrorString() const
268 if (shared_ptr
<QIODevice
> io
= ioDevice()) {
269 return io
->errorString();
271 return i18n("No output device");
274 virtual void doFinalize() = 0;
275 virtual void doCancel() = 0;
277 QString m_defaultLabel
;
278 QString m_customLabel
;
279 mutable cached
<QString
> m_errorString
;
280 bool m_isFinalized
: 1;
281 bool m_isFinalizing
: 1;
282 bool m_cancelPending
: 1;
284 bool m_binaryOpt
: 1;
287 class PipeOutput
: public OutputImplBase
290 explicit PipeOutput(assuan_fd_t fd
);
292 shared_ptr
<QIODevice
> ioDevice() const Q_DECL_OVERRIDE
296 void doFinalize() Q_DECL_OVERRIDE
{
299 void doCancel() Q_DECL_OVERRIDE
{
303 shared_ptr
< inhibit_close
<KDPipeIODevice
> > m_io
;
306 class ProcessStdInOutput
: public OutputImplBase
309 explicit ProcessStdInOutput(const QString
&cmd
, const QStringList
&args
, const QDir
&wd
);
311 shared_ptr
<QIODevice
> ioDevice() const Q_DECL_OVERRIDE
315 void doFinalize() Q_DECL_OVERRIDE
{
317 Make sure the data is written in the output here. If this
318 is not done the output will be written in small chunks
319 trough the eventloop causing an uncessary delay before
320 the process has even a chance to work and finish.
321 This delay is mainly noticabe on Windows where it can
322 take ~30 seconds to write out a 10MB file in the 512 byte
323 chunks gpgme serves. */
324 qCDebug(KLEOPATRA_LOG
) << "Waiting for " << m_proc
->bytesToWrite()
325 << " Bytes to be written";
326 while (m_proc
->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT
));
328 if (!m_proc
->isClosed())
332 m_proc
->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT
);
334 void doCancel() Q_DECL_OVERRIDE
{
336 QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT
, m_proc
.get(), &QProcess::kill
);
338 QString
label() const Q_DECL_OVERRIDE
;
341 QString
doErrorString() const Q_DECL_OVERRIDE
;
344 const QString m_command
;
345 const QStringList m_arguments
;
346 const shared_ptr
< redirect_close
<QProcess
> > m_proc
;
349 class FileOutput
: public OutputImplBase
352 explicit FileOutput(const QString
&fileName
, const shared_ptr
<OverwritePolicy
> &policy
);
355 qCDebug(KLEOPATRA_LOG
) << this;
358 QString
label() const Q_DECL_OVERRIDE
360 return QFileInfo(m_fileName
).fileName();
362 shared_ptr
<QIODevice
> ioDevice() const Q_DECL_OVERRIDE
366 void doFinalize() Q_DECL_OVERRIDE
;
367 void doCancel() Q_DECL_OVERRIDE
{
368 qCDebug(KLEOPATRA_LOG
) << this;
371 bool obtainOverwritePermission();
374 const QString m_fileName
;
375 shared_ptr
< TemporaryFile
> m_tmpFile
;
376 const shared_ptr
<OverwritePolicy
> m_policy
;
379 #ifndef QT_NO_CLIPBOARD
380 class ClipboardOutput
: public OutputImplBase
383 explicit ClipboardOutput(QClipboard::Mode mode
);
385 QString
label() const Q_DECL_OVERRIDE
;
386 shared_ptr
<QIODevice
> ioDevice() const Q_DECL_OVERRIDE
390 void doFinalize() Q_DECL_OVERRIDE
;
391 void doCancel() Q_DECL_OVERRIDE
{}
394 QString
doErrorString() const Q_DECL_OVERRIDE
399 const QClipboard::Mode m_mode
;
400 shared_ptr
<QBuffer
> m_buffer
;
402 #endif // QT_NO_CLIPBOARD
406 shared_ptr
<Output
> Output::createFromPipeDevice(assuan_fd_t fd
, const QString
&label
)
408 shared_ptr
<PipeOutput
> po(new PipeOutput(fd
));
409 po
->setDefaultLabel(label
);
413 PipeOutput::PipeOutput(assuan_fd_t fd
)
415 m_io(new inhibit_close
<KDPipeIODevice
>)
418 if (!m_io
->open(fd
, QIODevice::WriteOnly
))
419 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
420 i18n("Could not open FD %1 for writing",
424 shared_ptr
<Output
> Output::createFromFile(const QString
&fileName
, bool forceOverwrite
)
426 return createFromFile(fileName
, shared_ptr
<OverwritePolicy
>(new OverwritePolicy(0, forceOverwrite
? OverwritePolicy::Allow
: OverwritePolicy::Deny
)));
429 shared_ptr
<Output
> Output::createFromFile(const QString
&fileName
, const shared_ptr
<OverwritePolicy
> &policy
)
431 shared_ptr
<FileOutput
> fo(new FileOutput(fileName
, policy
));
432 qCDebug(KLEOPATRA_LOG
) << fo
.get();
436 FileOutput::FileOutput(const QString
&fileName
, const shared_ptr
<OverwritePolicy
> &policy
)
438 m_fileName(fileName
),
439 m_tmpFile(new TemporaryFile(fileName
)),
444 if (!m_tmpFile
->openNonInheritable())
445 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
446 i18n("Could not create temporary file for output \"%1\"", fileName
));
449 bool FileOutput::obtainOverwritePermission()
451 if (m_policy
->policy() != OverwritePolicy::Ask
) {
452 return m_policy
->policy() == OverwritePolicy::Allow
;
454 const int sel
= KMessageBox::questionYesNoCancel(m_policy
->parentWidget(), i18n("The file <b>%1</b> already exists.\n"
455 "Overwrite?", m_fileName
),
456 i18n("Overwrite Existing File?"),
457 KStandardGuiItem::overwrite(),
458 KGuiItem(i18n("Overwrite All")),
459 KStandardGuiItem::cancel());
460 if (sel
== KMessageBox::No
) { //Overwrite All
461 m_policy
->setPolicy(OverwritePolicy::Allow
);
463 return sel
== KMessageBox::Yes
|| sel
== KMessageBox::No
;
466 void FileOutput::doFinalize()
468 qCDebug(KLEOPATRA_LOG
) << this;
474 if (QFile::exists(file
)) {
480 kleo_assert(m_tmpFile
);
482 if (m_tmpFile
->isOpen()) {
486 const QString tmpFileName
= remover
.file
= m_tmpFile
->oldFileName();
488 m_tmpFile
->setAutoRemove(false);
489 QPointer
<QObject
> guard
= m_tmpFile
.get();
490 m_tmpFile
.reset(); // really close the file - needed on Windows for renaming :/
491 kleo_assert(!guard
); // if this triggers, we need to audit for holder of shared_ptr<QIODevice>s.
493 qCDebug(KLEOPATRA_LOG
) << this << " renaming " << tmpFileName
<< "->" << m_fileName
;
495 if (QFile::rename(tmpFileName
, m_fileName
)) {
496 qCDebug(KLEOPATRA_LOG
) << this << "succeeded";
500 qCDebug(KLEOPATRA_LOG
) << this << "failed";
502 if (!obtainOverwritePermission())
503 throw Exception(gpg_error(GPG_ERR_CANCELED
),
504 i18n("Overwriting declined"));
506 qCDebug(KLEOPATRA_LOG
) << this << "going to overwrite" << m_fileName
;
508 if (!QFile::remove(m_fileName
))
509 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
510 i18n("Could not remove file \"%1\" for overwriting.", m_fileName
));
512 qCDebug(KLEOPATRA_LOG
) << this << "succeeded, renaming " << tmpFileName
<< "->" << m_fileName
;
514 if (QFile::rename(tmpFileName
, m_fileName
)) {
515 qCDebug(KLEOPATRA_LOG
) << this << "succeeded";
519 qCDebug(KLEOPATRA_LOG
) << this << "failed";
521 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
522 i18n("Could not rename file \"%1\" to \"%2\"",
523 tmpFileName
, m_fileName
));
526 shared_ptr
<Output
> Output::createFromProcessStdIn(const QString
&command
)
528 return shared_ptr
<Output
>(new ProcessStdInOutput(command
, QStringList(), QDir::current()));
531 shared_ptr
<Output
> Output::createFromProcessStdIn(const QString
&command
, const QStringList
&args
)
533 return shared_ptr
<Output
>(new ProcessStdInOutput(command
, args
, QDir::current()));
536 shared_ptr
<Output
> Output::createFromProcessStdIn(const QString
&command
, const QStringList
&args
, const QDir
&wd
)
538 return shared_ptr
<Output
>(new ProcessStdInOutput(command
, args
, wd
));
541 ProcessStdInOutput::ProcessStdInOutput(const QString
&cmd
, const QStringList
&args
, const QDir
&wd
)
545 m_proc(new redirect_close
<QProcess
>)
547 qCDebug(KLEOPATRA_LOG
) << "cd" << wd
.absolutePath() << endl
<< cmd
<< args
;
549 throw Exception(gpg_error(GPG_ERR_INV_ARG
),
550 i18n("Command not specified"));
551 m_proc
->setWorkingDirectory(wd
.absolutePath());
552 m_proc
->start(cmd
, args
);
553 m_proc
->setReadChannel(QProcess::StandardError
);
554 if (!m_proc
->waitForStarted())
555 throw Exception(gpg_error(GPG_ERR_EIO
),
556 i18n("Could not start %1 process: %2", cmd
, m_proc
->errorString()));
559 QString
ProcessStdInOutput::label() const
562 return OutputImplBase::label();
564 // output max. 3 arguments
565 const QString cmdline
= (QStringList(m_command
) + m_arguments
.mid(0, 3)).join(QStringLiteral(" "));
566 if (m_arguments
.size() > 3) {
567 return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline
);
569 return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline
);
573 QString
ProcessStdInOutput::doErrorString() const
576 if (m_proc
->exitStatus() == QProcess::NormalExit
&& m_proc
->exitCode() == 0) {
579 if (m_proc
->error() == QProcess::UnknownError
)
580 return i18n("Error while running %1: %2", m_command
,
581 QString::fromLocal8Bit(m_proc
->readAllStandardError().trimmed().constData()));
583 return i18n("Failed to execute %1: %2", m_command
, m_proc
->errorString());
587 #ifndef QT_NO_CLIPBOARD
588 shared_ptr
<Output
> Output::createFromClipboard()
590 return shared_ptr
<Output
>(new ClipboardOutput(QClipboard::Clipboard
));
593 ClipboardOutput::ClipboardOutput(QClipboard::Mode mode
)
596 m_buffer(new QBuffer
)
599 if (!m_buffer
->open(QIODevice::WriteOnly
))
600 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
601 i18n("Could not write to clipboard"));
604 QString
ClipboardOutput::label() const
607 case QClipboard::Clipboard
:
608 return i18n("Clipboard");
609 case QClipboard::FindBuffer
:
610 return i18n("Find buffer");
611 case QClipboard::Selection
:
612 return i18n("Selection");
617 void ClipboardOutput::doFinalize()
619 if (m_buffer
->isOpen()) {
622 if (QClipboard
*const cb
= QApplication::clipboard()) {
623 cb
->setText(QString::fromUtf8(m_buffer
->data()));
625 throw Exception(gpg_error(GPG_ERR_EIO
),
626 i18n("Could not find clipboard"));
628 #endif // QT_NO_CLIPBOARD