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 <kleo/exception.h>
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 /* reimp */ void close()
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 /* reimp */ QString
label() const
198 return m_customLabel
.isEmpty() ? m_defaultLabel
: m_customLabel
;
200 /* reimp */ void setLabel(const QString
&label
)
202 m_customLabel
= label
;
204 void setDefaultLabel(const QString
&l
)
208 /* reimp */ void setBinaryOpt(bool value
)
212 /* reimp */ bool binaryOpt() const
217 /* reimp */ QString
errorString() const
219 if (m_errorString
.dirty()) {
220 m_errorString
= doErrorString();
222 return m_errorString
;
225 /* reimp */ bool isFinalized() const
227 return m_isFinalized
;
229 /* reimp */ void finalize()
231 qCDebug(KLEOPATRA_LOG
) << this;
232 if (m_isFinalized
|| m_isFinalizing
) {
235 m_isFinalizing
= true;
239 m_isFinalizing
= false;
242 m_isFinalizing
= false;
243 m_isFinalized
= true;
244 if (m_cancelPending
) {
249 /* reimp */ void cancel()
251 qCDebug(KLEOPATRA_LOG
) << this;
252 if (m_isFinalizing
) {
253 m_cancelPending
= true;
254 } else if (!m_canceled
) {
255 m_isFinalizing
= true;
259 m_isFinalizing
= false;
260 m_isFinalized
= true;
265 virtual QString
doErrorString() const
267 if (shared_ptr
<QIODevice
> io
= ioDevice()) {
268 return io
->errorString();
270 return i18n("No output device");
273 virtual void doFinalize() = 0;
274 virtual void doCancel() = 0;
276 QString m_defaultLabel
;
277 QString m_customLabel
;
278 mutable cached
<QString
> m_errorString
;
279 bool m_isFinalized
: 1;
280 bool m_isFinalizing
: 1;
281 bool m_cancelPending
: 1;
283 bool m_binaryOpt
: 1;
286 class PipeOutput
: public OutputImplBase
289 explicit PipeOutput(assuan_fd_t fd
);
291 /* reimp */ shared_ptr
<QIODevice
> ioDevice() const
295 /* reimp */ void doFinalize()
299 /* reimp */ void doCancel()
304 shared_ptr
< inhibit_close
<KDPipeIODevice
> > m_io
;
307 class ProcessStdInOutput
: public OutputImplBase
310 explicit ProcessStdInOutput(const QString
&cmd
, const QStringList
&args
, const QDir
&wd
);
312 /* reimp */ shared_ptr
<QIODevice
> ioDevice() const
316 /* reimp */ void doFinalize()
319 Make sure the data is written in the output here. If this
320 is not done the output will be written in small chunks
321 trough the eventloop causing an uncessary delay before
322 the process has even a chance to work and finish.
323 This delay is mainly noticabe on Windows where it can
324 take ~30 seconds to write out a 10MB file in the 512 byte
325 chunks gpgme serves. */
326 qCDebug(KLEOPATRA_LOG
) << "Waiting for " << m_proc
->bytesToWrite()
327 << " Bytes to be written";
328 while (m_proc
->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT
));
330 if (!m_proc
->isClosed()) {
333 m_proc
->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT
);
335 /* reimp */ void doCancel()
338 QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT
, m_proc
.get(), SLOT(kill()));
340 /* reimp */ QString
label() const;
343 /* reimp */ QString
doErrorString() const;
346 const QString m_command
;
347 const QStringList m_arguments
;
348 const shared_ptr
< redirect_close
<QProcess
> > m_proc
;
351 class FileOutput
: public OutputImplBase
354 explicit FileOutput(const QString
&fileName
, const shared_ptr
<OverwritePolicy
> &policy
);
357 qCDebug(KLEOPATRA_LOG
) << this;
360 /* reimp */ QString
label() const
362 return QFileInfo(m_fileName
).fileName();
364 /* reimp */ shared_ptr
<QIODevice
> ioDevice() const
368 /* reimp */ void doFinalize();
369 /* reimp */ void doCancel()
371 qCDebug(KLEOPATRA_LOG
) << this;
374 bool obtainOverwritePermission();
377 const QString m_fileName
;
378 shared_ptr
< TemporaryFile
> m_tmpFile
;
379 const shared_ptr
<OverwritePolicy
> m_policy
;
382 #ifndef QT_NO_CLIPBOARD
383 class ClipboardOutput
: public OutputImplBase
386 explicit ClipboardOutput(QClipboard::Mode mode
);
388 /* reimp */ QString
label() const;
389 /* reimp */ shared_ptr
<QIODevice
> ioDevice() const
393 /* reimp */ void doFinalize();
394 /* reimp */ void doCancel() {}
397 /* reimp */ QString
doErrorString() const
402 const QClipboard::Mode m_mode
;
403 shared_ptr
<QBuffer
> m_buffer
;
405 #endif // QT_NO_CLIPBOARD
409 shared_ptr
<Output
> Output::createFromPipeDevice(assuan_fd_t fd
, const QString
&label
)
411 shared_ptr
<PipeOutput
> po(new PipeOutput(fd
));
412 po
->setDefaultLabel(label
);
416 PipeOutput::PipeOutput(assuan_fd_t fd
)
418 m_io(new inhibit_close
<KDPipeIODevice
>)
421 if (!m_io
->open(fd
, QIODevice::WriteOnly
))
422 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
423 i18n("Could not open FD %1 for writing",
427 shared_ptr
<Output
> Output::createFromFile(const QString
&fileName
, bool forceOverwrite
)
429 return createFromFile(fileName
, shared_ptr
<OverwritePolicy
>(new OverwritePolicy(0, forceOverwrite
? OverwritePolicy::Allow
: OverwritePolicy::Deny
)));
432 shared_ptr
<Output
> Output::createFromFile(const QString
&fileName
, const shared_ptr
<OverwritePolicy
> &policy
)
434 shared_ptr
<FileOutput
> fo(new FileOutput(fileName
, policy
));
435 qCDebug(KLEOPATRA_LOG
) << fo
.get();
439 FileOutput::FileOutput(const QString
&fileName
, const shared_ptr
<OverwritePolicy
> &policy
)
441 m_fileName(fileName
),
442 m_tmpFile(new TemporaryFile(fileName
)),
447 if (!m_tmpFile
->openNonInheritable())
448 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
449 i18n("Could not create temporary file for output \"%1\"", fileName
));
452 bool FileOutput::obtainOverwritePermission()
454 if (m_policy
->policy() != OverwritePolicy::Ask
) {
455 return m_policy
->policy() == OverwritePolicy::Allow
;
457 const int sel
= KMessageBox::questionYesNoCancel(m_policy
->parentWidget(), i18n("The file <b>%1</b> already exists.\n"
458 "Overwrite?", m_fileName
),
459 i18n("Overwrite Existing File?"),
460 KStandardGuiItem::overwrite(),
461 KGuiItem(i18n("Overwrite All")),
462 KStandardGuiItem::cancel());
463 if (sel
== KMessageBox::No
) { //Overwrite All
464 m_policy
->setPolicy(OverwritePolicy::Allow
);
466 return sel
== KMessageBox::Yes
|| sel
== KMessageBox::No
;
469 void FileOutput::doFinalize()
471 qCDebug(KLEOPATRA_LOG
) << this;
477 if (QFile::exists(file
)) {
483 kleo_assert(m_tmpFile
);
485 if (m_tmpFile
->isOpen()) {
489 const QString tmpFileName
= remover
.file
= m_tmpFile
->oldFileName();
491 m_tmpFile
->setAutoRemove(false);
492 QPointer
<QObject
> guard
= m_tmpFile
.get();
493 m_tmpFile
.reset(); // really close the file - needed on Windows for renaming :/
494 kleo_assert(!guard
); // if this triggers, we need to audit for holder of shared_ptr<QIODevice>s.
496 qCDebug(KLEOPATRA_LOG
) << this << " renaming " << tmpFileName
<< "->" << m_fileName
;
498 if (QFile::rename(tmpFileName
, m_fileName
)) {
499 qCDebug(KLEOPATRA_LOG
) << this << "succeeded";
503 qCDebug(KLEOPATRA_LOG
) << this << "failed";
505 if (!obtainOverwritePermission())
506 throw Exception(gpg_error(GPG_ERR_CANCELED
),
507 i18n("Overwriting declined"));
509 qCDebug(KLEOPATRA_LOG
) << this << "going to overwrite" << m_fileName
;
511 if (!QFile::remove(m_fileName
))
512 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
513 i18n("Could not remove file \"%1\" for overwriting.", m_fileName
));
515 qCDebug(KLEOPATRA_LOG
) << this << "succeeded, renaming " << tmpFileName
<< "->" << m_fileName
;
517 if (QFile::rename(tmpFileName
, m_fileName
)) {
518 qCDebug(KLEOPATRA_LOG
) << this << "succeeded";
522 qCDebug(KLEOPATRA_LOG
) << this << "failed";
524 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
525 i18n("Could not rename file \"%1\" to \"%2\"",
526 tmpFileName
, m_fileName
));
529 shared_ptr
<Output
> Output::createFromProcessStdIn(const QString
&command
)
531 return shared_ptr
<Output
>(new ProcessStdInOutput(command
, QStringList(), QDir::current()));
534 shared_ptr
<Output
> Output::createFromProcessStdIn(const QString
&command
, const QStringList
&args
)
536 return shared_ptr
<Output
>(new ProcessStdInOutput(command
, args
, QDir::current()));
539 shared_ptr
<Output
> Output::createFromProcessStdIn(const QString
&command
, const QStringList
&args
, const QDir
&wd
)
541 return shared_ptr
<Output
>(new ProcessStdInOutput(command
, args
, wd
));
544 ProcessStdInOutput::ProcessStdInOutput(const QString
&cmd
, const QStringList
&args
, const QDir
&wd
)
548 m_proc(new redirect_close
<QProcess
>)
550 qCDebug(KLEOPATRA_LOG
) << "cd" << wd
.absolutePath() << endl
<< cmd
<< args
;
552 throw Exception(gpg_error(GPG_ERR_INV_ARG
),
553 i18n("Command not specified"));
554 m_proc
->setWorkingDirectory(wd
.absolutePath());
555 m_proc
->start(cmd
, args
);
556 m_proc
->setReadChannel(QProcess::StandardError
);
557 if (!m_proc
->waitForStarted())
558 throw Exception(gpg_error(GPG_ERR_EIO
),
559 i18n("Could not start %1 process: %2", cmd
, m_proc
->errorString()));
562 QString
ProcessStdInOutput::label() const
565 return OutputImplBase::label();
567 // output max. 3 arguments
568 const QString cmdline
= (QStringList(m_command
) + m_arguments
.mid(0, 3)).join(QStringLiteral(" "));
569 if (m_arguments
.size() > 3) {
570 return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline
);
572 return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline
);
576 QString
ProcessStdInOutput::doErrorString() const
579 if (m_proc
->exitStatus() == QProcess::NormalExit
&& m_proc
->exitCode() == 0) {
582 if (m_proc
->error() == QProcess::UnknownError
)
583 return i18n("Error while running %1: %2", m_command
,
584 QString::fromLocal8Bit(m_proc
->readAllStandardError().trimmed().constData()));
586 return i18n("Failed to execute %1: %2", m_command
, m_proc
->errorString());
590 #ifndef QT_NO_CLIPBOARD
591 shared_ptr
<Output
> Output::createFromClipboard()
593 return shared_ptr
<Output
>(new ClipboardOutput(QClipboard::Clipboard
));
596 ClipboardOutput::ClipboardOutput(QClipboard::Mode mode
)
599 m_buffer(new QBuffer
)
602 if (!m_buffer
->open(QIODevice::WriteOnly
))
603 throw Exception(errno
? gpg_error_from_errno(errno
) : gpg_error(GPG_ERR_EIO
),
604 i18n("Could not write to clipboard"));
607 QString
ClipboardOutput::label() const
610 case QClipboard::Clipboard
:
611 return i18n("Clipboard");
612 case QClipboard::FindBuffer
:
613 return i18n("Find buffer");
614 case QClipboard::Selection
:
615 return i18n("Selection");
620 void ClipboardOutput::doFinalize()
622 if (m_buffer
->isOpen()) {
625 if (QClipboard
*const cb
= QApplication::clipboard()) {
626 cb
->setText(QString::fromUtf8(m_buffer
->data()));
628 throw Exception(gpg_error(GPG_ERR_EIO
),
629 i18n("Could not find clipboard"));
631 #endif // QT_NO_CLIPBOARD