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 }