File indexing completed on 2024-06-23 05:14:17
0001 // SPDX-FileCopyrightText: 2024 g10 Code GmbH 0002 // SPDX-License-Identifier: GPL-2.0-or-later 0003 0004 #include "keyexportdraghandler.h" 0005 0006 #include "kleopatra_debug.h" 0007 0008 #include <Libkleo/Formatting> 0009 #include <Libkleo/KeyList> 0010 0011 #include <QGpgME/ExportJob> 0012 #include <QGpgME/Protocol> 0013 0014 #include <gpgme++/key.h> 0015 0016 // needed for GPGME_VERSION_NUMBER 0017 #include <gpgme.h> 0018 0019 #include <QApplication> 0020 #include <QFileInfo> 0021 #include <QRegularExpression> 0022 #include <QTemporaryFile> 0023 #include <QUrl> 0024 0025 #include <KFileUtils> 0026 #include <KLocalizedString> 0027 0028 using namespace GpgME; 0029 using namespace Kleo; 0030 0031 static QStringList supportedMimeTypes = { 0032 QStringLiteral("text/uri-list"), 0033 QStringLiteral("application/pgp-keys"), 0034 QStringLiteral("text/plain"), 0035 }; 0036 0037 class KeyExportMimeData : public QMimeData 0038 { 0039 public: 0040 QVariant retrieveData(const QString &mimeType, QMetaType type) const override 0041 { 0042 Q_UNUSED(type); 0043 QByteArray pgpData; 0044 QByteArray smimeData; 0045 0046 #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0 0047 if (!pgpFprs.isEmpty()) { 0048 auto job = QGpgME::openpgp()->publicKeyExportJob(true); 0049 job->exec(pgpFprs, pgpData); 0050 } 0051 if (!smimeFprs.isEmpty()) { 0052 auto job = QGpgME::smime()->publicKeyExportJob(true); 0053 job->exec(smimeFprs, smimeData); 0054 } 0055 #endif 0056 0057 if (mimeType == QStringLiteral("text/uri-list")) { 0058 file->open(); 0059 file->write(pgpData + smimeData); 0060 file->close(); 0061 return QUrl(QStringLiteral("file://%1").arg(file->fileName())); 0062 } else if (mimeType == QStringLiteral("application/pgp-keys")) { 0063 return pgpData; 0064 } else if (mimeType == QStringLiteral("text/plain")) { 0065 QByteArray data = pgpData + smimeData; 0066 return data; 0067 } 0068 0069 return {}; 0070 } 0071 bool hasFormat(const QString &mimeType) const override 0072 { 0073 return supportedMimeTypes.contains(mimeType); 0074 } 0075 QStringList formats() const override 0076 { 0077 return supportedMimeTypes; 0078 } 0079 QStringList pgpFprs; 0080 QStringList smimeFprs; 0081 QString name; 0082 QTemporaryFile *file; 0083 }; 0084 0085 KeyExportDragHandler::KeyExportDragHandler() 0086 { 0087 } 0088 0089 QStringList KeyExportDragHandler::mimeTypes() const 0090 { 0091 return supportedMimeTypes; 0092 } 0093 0094 Qt::ItemFlags KeyExportDragHandler::flags(const QModelIndex &index) const 0095 { 0096 Q_UNUSED(index); 0097 return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; 0098 } 0099 0100 static QString suggestFileName(const QString &fileName) 0101 { 0102 const QFileInfo fileInfo{fileName}; 0103 const QString path = fileInfo.absolutePath(); 0104 const QString newFileName = KFileUtils::suggestName(QUrl::fromLocalFile(path), fileInfo.fileName()); 0105 return path + QLatin1Char{'/'} + newFileName; 0106 } 0107 0108 QMimeData *KeyExportDragHandler::mimeData(const QModelIndexList &indexes) const 0109 { 0110 auto mimeData = new KeyExportMimeData(); 0111 0112 QSet<QString> pgpFprs; 0113 QSet<QString> smimeFprs; 0114 0115 // apparently we're getting an index for each column even though we're selecting whole rows 0116 // so figure out whether we're actually selecting more than one row 0117 bool singleRow = true; 0118 int row = indexes[0].row(); 0119 auto parent = indexes[0].parent(); 0120 0121 for (const auto &index : indexes) { 0122 auto key = index.data(KeyList::KeyRole).value<Key>(); 0123 0124 (key.protocol() == GpgME::OpenPGP ? pgpFprs : smimeFprs) += QString::fromLatin1(key.primaryFingerprint()); 0125 0126 if (index.row() != row || index.parent() != parent) { 0127 singleRow = false; 0128 } 0129 } 0130 0131 QString name; 0132 if (singleRow) { 0133 auto key = indexes[0].data(KeyList::KeyRole).value<Key>(); 0134 auto keyName = Formatting::prettyName(key); 0135 if (keyName.isEmpty()) { 0136 keyName = Formatting::prettyEMail(key); 0137 } 0138 name = QStringLiteral("%1_%2_public.%3") 0139 .arg(keyName, Formatting::prettyKeyID(key.shortKeyID()), pgpFprs.isEmpty() ? QStringLiteral("pem") : QStringLiteral("asc")); 0140 } else { 0141 name = i18nc("A generic filename for exported certificates", "certificates.%1", pgpFprs.isEmpty() ? QStringLiteral("pem") : QStringLiteral("asc")); 0142 } 0143 // The file is deliberately not destroyed when the mimedata is destroyed, to give the receiver more time to read it. 0144 mimeData->file = new QTemporaryFile(qApp); 0145 mimeData->file->setFileTemplate(name); 0146 mimeData->file->open(); 0147 auto path = mimeData->file->fileName().remove(QRegularExpression(QStringLiteral("\\.[^.]+$"))); 0148 0149 if (QFileInfo(path).exists()) { 0150 path = suggestFileName(path); 0151 } 0152 mimeData->file->rename(path); 0153 0154 mimeData->pgpFprs = QStringList(pgpFprs.begin(), pgpFprs.end()); 0155 mimeData->smimeFprs = QStringList(smimeFprs.begin(), smimeFprs.end()); 0156 return mimeData; 0157 }