File indexing completed on 2025-01-19 04:46:49
0001 /* 0002 SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "gnupgwksurlhandler.h" 0008 #include "gnupgwks_debug.h" 0009 #include "gnupgwksmessagepart.h" 0010 0011 #include <QProcess> 0012 #include <QString> 0013 #include <QUrlQuery> 0014 0015 #include <MessageViewer/Viewer> 0016 #include <MimeTreeParser/BodyPart> 0017 #include <MimeTreeParser/NodeHelper> 0018 0019 #include <QGpgME/Protocol> 0020 #include <QGpgME/WKSPublishJob> 0021 0022 #include <Akonadi/MessageQueueJob> 0023 #include <MailTransport/Transport> 0024 #include <MailTransport/TransportManager> 0025 0026 #include <KIdentityManagementCore/Identity> 0027 #include <KIdentityManagementCore/IdentityManager> 0028 0029 #include <KMime/Util> 0030 0031 #include <Akonadi/ItemDeleteJob> 0032 0033 #include <KLocalizedString> 0034 0035 using namespace MimeTreeParser::Interface; 0036 0037 bool ApplicationGnuPGWKSUrlHandler::handleContextMenuRequest(BodyPart *, const QString &, const QPoint &) const 0038 { 0039 return false; 0040 } 0041 0042 QString ApplicationGnuPGWKSUrlHandler::name() const 0043 { 0044 return QStringLiteral("ApplicationGnuPGWKSUrlHandler"); 0045 } 0046 0047 bool ApplicationGnuPGWKSUrlHandler::handleClick(MessageViewer::Viewer *viewerInstance, BodyPart *part, const QString &path) const 0048 { 0049 Q_UNUSED(viewerInstance) 0050 0051 if (!path.startsWith(QLatin1StringView("gnupgwks?"))) { 0052 return false; 0053 } 0054 0055 const QUrlQuery q(path.mid(sizeof("gnupgwks?") - 1)); 0056 if (q.queryItemValue(QStringLiteral("action")) == QLatin1StringView("show")) { 0057 const QString progFullPath = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); 0058 if (progFullPath.isEmpty() 0059 || !QProcess::startDetached(QStringLiteral("kleopatra"), {QStringLiteral("--query"), q.queryItemValue(QStringLiteral("fpr"))})) { 0060 return false; 0061 } 0062 return true; 0063 } else if (q.queryItemValue(QStringLiteral("action")) == QLatin1StringView("confirm")) { 0064 GnuPGWKSMessagePart mp(part); 0065 if (!sendConfirmation(viewerInstance, mp)) { 0066 part->nodeHelper()->setProperty((QStringLiteral("__GnuPGWKS") + mp.fingerprint()).toLatin1().constData(), QStringLiteral("error")); 0067 } 0068 return true; 0069 } 0070 0071 return false; 0072 } 0073 0074 QString ApplicationGnuPGWKSUrlHandler::statusBarMessage(BodyPart *part, const QString &path) const 0075 { 0076 Q_UNUSED(part) 0077 0078 if (!path.startsWith(QLatin1StringView("gnupgwks?"))) { 0079 return {}; 0080 } 0081 0082 const QUrlQuery q(path.mid(sizeof("gnupgwks?") - 1)); 0083 const QString actionStr = q.queryItemValue(QStringLiteral("action")); 0084 if (actionStr == QLatin1StringView("show")) { 0085 return i18n("Display key details"); 0086 } else if (actionStr == QLatin1StringView("confirm")) { 0087 return i18n("Publish the key"); 0088 } 0089 return {}; 0090 } 0091 0092 QByteArray ApplicationGnuPGWKSUrlHandler::createConfirmation(const KMime::Message::Ptr &msg) const 0093 { 0094 auto job = QGpgME::openpgp()->wksPublishJob(); 0095 QEventLoop el; 0096 QByteArray result; 0097 QObject::connect(job, 0098 &QGpgME::WKSPublishJob::result, 0099 [&el, &result](const GpgME::Error &, const QByteArray &returnedData, const QByteArray &returnedError) { 0100 if (returnedData.isEmpty()) { 0101 qCWarning(GNUPGWKS_LOG) << "GPG:" << returnedError; 0102 } 0103 result = returnedData; 0104 el.quit(); 0105 }); 0106 job->startReceive(msg->encodedContent()); 0107 el.exec(); 0108 0109 return result; 0110 } 0111 0112 bool ApplicationGnuPGWKSUrlHandler::sendConfirmation(MessageViewer::Viewer *viewerInstance, const GnuPGWKSMessagePart &mp) const 0113 { 0114 const QByteArray data = createConfirmation(viewerInstance->message()); 0115 if (data.isEmpty()) { 0116 return false; 0117 } 0118 0119 auto msg = KMime::Message::Ptr::create(); 0120 msg->setContent(KMime::CRLFtoLF(data)); 0121 msg->parse(); 0122 0123 // Find identity 0124 const auto identity = KIdentityManagementCore::IdentityManager::self()->identityForAddress(mp.address()); 0125 const bool nullIdentity = (identity == KIdentityManagementCore::Identity::null()); 0126 if (!nullIdentity) { 0127 auto x_header = new KMime::Headers::Generic("X-KMail-Identity"); 0128 x_header->from7BitString(QByteArray::number(identity.uoid())); 0129 msg->setHeader(x_header); 0130 } 0131 0132 // Find transport set in the identity, fallback to default transport 0133 auto transportMgr = MailTransport::TransportManager::self(); 0134 const bool identityHasTransport = !identity.transport().isEmpty(); 0135 int transportId = -1; 0136 if (!nullIdentity && identityHasTransport) { 0137 transportId = identity.transport().toInt(); 0138 } else { 0139 transportId = transportMgr->defaultTransportId(); 0140 } 0141 // No transport exists, ask user to create one 0142 if (transportId == -1) { 0143 if (!transportMgr->showTransportCreationDialog(nullptr, MailTransport::TransportManager::IfNoTransportExists)) { 0144 return false; 0145 } 0146 transportId = transportMgr->defaultTransportId(); 0147 } 0148 auto header = new KMime::Headers::Generic("X-KMail-Transport"); 0149 header->fromUnicodeString(QString::number(transportId), "utf-8"); 0150 msg->setHeader(header); 0151 0152 // Build the message 0153 msg->assemble(); 0154 0155 // Move to outbox 0156 auto transport = transportMgr->transportById(transportId); 0157 auto job = new Akonadi::MessageQueueJob; 0158 job->addressAttribute().setTo({msg->to(false)->asUnicodeString()}); 0159 job->transportAttribute().setTransportId(transport->id()); 0160 job->addressAttribute().setFrom(msg->from(false)->asUnicodeString()); 0161 job->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete); 0162 job->sentBehaviourAttribute().setSendSilently(true); 0163 job->setMessage(msg); 0164 0165 // Send 0166 if (!job->exec()) { 0167 qCWarning(GNUPGWKS_LOG) << "Error queuing message in output:" << job->errorText(); 0168 return false; 0169 } 0170 0171 // Delete the original request 0172 // Don't use viewerInstance->deleteMessage(), which triggers Move To Trash, 0173 // we want to get rid of the message for good. 0174 new Akonadi::ItemDeleteJob(viewerInstance->messageItem()); 0175 return true; 0176 }