Messagelib

autocryptheadersjob.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Sandro Knauß <sknauss@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "job/autocryptheadersjob.h"
8
9#include "contentjobbase_p.h"
10
11#include "job/singlepartjob.h"
12#include "utils/util_p.h"
13
14#include <QGpgME/ExportJob>
15#include <QGpgME/Protocol>
16#include <gpgme++/context.h>
17
18#include <KCodecs>
19#include <KLocalizedString>
20#include <KMime/Content>
21#include <KMime/Headers>
22#include <Libkleo/Formatting>
23
24#include <QByteArray>
25
26#include <map>
27
28using namespace MessageComposer;
29
30class MessageComposer::AutocryptHeadersJobPrivate : public ContentJobBasePrivate
31{
32public:
33 AutocryptHeadersJobPrivate(AutocryptHeadersJob *qq)
34 : ContentJobBasePrivate(qq)
35 {
36 }
37
38 ~AutocryptHeadersJobPrivate() override
39 {
40 // clean up in case of cancelled job
41 for (const auto &[key, header] : gossipHeaders) {
42 delete header;
43 }
44 gossipHeaders.clear();
45 }
46
47 void emitGpgError(const GpgME::Error &error);
48 void emitNotFoundError(const QByteArray &addr, const QByteArray &fingerprint);
49 void fillHeaderData(KMime::Headers::Generic *header, const QByteArray &addr, bool preferEncrypted, const QByteArray &keydata);
50 void finishOnLastSubJob();
51
52 KMime::Content *content = nullptr;
53 KMime::Message *skeletonMessage = nullptr;
54 // used to ensure consistent order based on key order, not random one by async subjobs delivering
55 std::map<QByteArray, KMime::Headers::Generic *> gossipHeaders;
56
57 bool preferEncrypted = false;
58 int subJobs = 0;
59
60 QString gnupgHome;
61 GpgME::Key recipientKey;
62 std::vector<GpgME::Key> gossipKeys;
63
64 Q_DECLARE_PUBLIC(AutocryptHeadersJob)
65};
66
67void AutocryptHeadersJobPrivate::finishOnLastSubJob()
68{
69 Q_Q(AutocryptHeadersJob);
70
71 if (subJobs > 0) {
72 return;
73 }
74
75 for (const auto &[key, header] : gossipHeaders) {
76 content->appendHeader(header);
77 }
78 gossipHeaders.clear();
79 resultContent = content;
80
81 q->emitResult();
82}
83
84void AutocryptHeadersJobPrivate::emitGpgError(const GpgME::Error &error)
85{
86 Q_Q(AutocryptHeadersJob);
87
88 Q_ASSERT(error);
89 const QString msg = i18n(
90 "<p>An error occurred while trying to export "
91 "the key from the backend:</p>"
92 "<p><b>%1</b></p>",
93 Kleo::Formatting::errorAsString(error));
94 q->setError(KJob::UserDefinedError);
95 q->setErrorText(msg);
96 q->emitResult();
97}
98
99void AutocryptHeadersJobPrivate::emitNotFoundError(const QByteArray &addr, const QByteArray &fingerprint)
100{
101 Q_Q(AutocryptHeadersJob);
102 const QString msg = i18n(
103 "<p>An error occurred while trying to export "
104 "the key from the backend:</p>"
105 "<p><b>No valid key found for user %1 (%2)</b></p>",
107 QString::fromLatin1(fingerprint));
108 q->setError(KJob::UserDefinedError);
109 q->setErrorText(msg);
110 q->emitResult();
111}
112
113void AutocryptHeadersJobPrivate::fillHeaderData(KMime::Headers::Generic *header, const QByteArray &addr, bool preferEncrypted, const QByteArray &keydata)
114{
115 QByteArray parameters = "addr=" + addr + "; ";
116 if (preferEncrypted) {
117 parameters += "prefer-encrypt=mutual; ";
118 }
119 parameters += "keydata=\n ";
120 const auto encoded = KCodecs::base64Encode(keydata).replace('\n', QByteArray());
121 const auto length = encoded.size();
122 const auto lineLength = 76;
123 auto start = 0;
124 auto column = 1;
125 while (start < length) {
126 const auto midLength = std::min<int>(length - start, lineLength - column);
127 parameters += encoded.mid(start, midLength);
128 start += midLength;
129 column += midLength;
130 if (column >= lineLength) {
131 parameters += "\n ";
132 column = 1;
133 }
134 }
135 header->from7BitString(parameters);
136}
137
138AutocryptHeadersJob::AutocryptHeadersJob(QObject *parent)
139 : ContentJobBase(*new AutocryptHeadersJobPrivate(this), parent)
140{
141}
142
143AutocryptHeadersJob::~AutocryptHeadersJob() = default;
144
145void AutocryptHeadersJob::setContent(KMime::Content *content)
146{
147 Q_D(AutocryptHeadersJob);
148
149 d->content = content;
150 if (content) {
151 d->content->assemble();
152 }
153}
154
155void AutocryptHeadersJob::setSkeletonMessage(KMime::Message *skeletonMessage)
156{
157 Q_D(AutocryptHeadersJob);
158
159 d->skeletonMessage = skeletonMessage;
160}
161
162void AutocryptHeadersJob::setGnupgHome(const QString &path)
163{
164 Q_D(AutocryptHeadersJob);
165
166 d->gnupgHome = path;
167}
168
169void AutocryptHeadersJob::setSenderKey(const GpgME::Key &key)
170{
171 Q_D(AutocryptHeadersJob);
172
173 d->recipientKey = key;
174}
175
176void AutocryptHeadersJob::setPreferEncrypted(bool preferEncrypted)
177{
178 Q_D(AutocryptHeadersJob);
179
180 d->preferEncrypted = preferEncrypted;
181}
182
183void AutocryptHeadersJob::setGossipKeys(const std::vector<GpgME::Key> &gossipKeys)
184{
185 Q_D(AutocryptHeadersJob);
186
187 d->gossipKeys = gossipKeys;
188}
189
190void AutocryptHeadersJob::process()
191{
192 Q_D(AutocryptHeadersJob);
193 Q_ASSERT(d->resultContent == nullptr); // Not processed before.
194
195 // if setContent hasn't been called, we assume that a subjob was added
196 // and we want to use that
197 if (!d->content) {
198 Q_ASSERT(d->subjobContents.size() == 1);
199 d->content = d->subjobContents.constFirst();
200 }
201
202 auto job = QGpgME::openpgp()->publicKeyExportJob(false);
203 Q_ASSERT(job);
204
205 if (!d->gnupgHome.isEmpty()) {
206 QGpgME::Job::context(job)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData());
207 }
208 if (!d->recipientKey.isNull() && !d->recipientKey.isInvalid()) {
209 connect(job, &QGpgME::ExportJob::result, this, [this, d](const GpgME::Error &error, const QByteArray &keydata) {
210 d->subJobs--;
212 // When the job already has failed do nothing.
213 return;
214 }
215 if (error) {
216 d->emitGpgError(error);
217 return;
218 }
219 if (keydata.isEmpty()) {
220 d->emitNotFoundError(d->skeletonMessage->from()->addresses()[0], d->recipientKey.primaryFingerprint());
221 return;
222 }
223
224 auto autocrypt = new KMime::Headers::Generic("Autocrypt");
225 d->fillHeaderData(autocrypt, d->skeletonMessage->from()->addresses()[0], d->preferEncrypted, keydata);
226
227 d->skeletonMessage->setHeader(autocrypt);
228 d->skeletonMessage->assemble();
229
230 d->finishOnLastSubJob();
231 });
232 d->subJobs++;
233 job->start(QStringList(QString::fromLatin1(d->recipientKey.primaryFingerprint())));
234 job->setExportFlags(GpgME::Context::ExportMinimal);
235 }
236
237 const auto keys = d->gossipKeys;
238 for (const auto &key : keys) {
239 if (QByteArray(key.primaryFingerprint()) == QByteArray(d->recipientKey.primaryFingerprint())) {
240 continue;
241 }
242
243 auto gossipJob = QGpgME::openpgp()->publicKeyExportJob(false);
244 Q_ASSERT(gossipJob);
245
246 if (!d->gnupgHome.isEmpty()) {
247 QGpgME::Job::context(gossipJob)->setEngineHomeDirectory(d->gnupgHome.toUtf8().constData());
248 }
249
250 connect(gossipJob, &QGpgME::ExportJob::result, this, [this, d, key](const GpgME::Error &error, const QByteArray &keydata) {
251 d->subJobs--;
253 // When the job already has failed do nothing.
254 return;
255 }
256 if (error) {
257 d->emitGpgError(error);
258 return;
259 }
260 if (keydata.isEmpty()) {
261 d->emitNotFoundError(key.userID(0).email(), key.primaryFingerprint());
262 return;
263 }
264
265 auto header = new KMime::Headers::Generic("Autocrypt-Gossip");
266 d->fillHeaderData(header, key.userID(0).email(), false, keydata);
267
268 d->gossipHeaders.insert({QByteArray(key.primaryFingerprint()), header});
269
270 d->finishOnLastSubJob();
271 });
272
273 d->subJobs++;
274 gossipJob->start(QStringList(QString::fromLatin1(key.primaryFingerprint())));
275 gossipJob->setExportFlags(GpgME::Context::ExportMinimal);
276 }
277 if (d->subJobs == 0) {
278 d->resultContent = d->content;
279 emitResult();
280 }
281}
282
283#include "moc_autocryptheadersjob.cpp"
void emitResult()
int error() const
void appendHeader(Headers::Base *h)
void from7BitString(QByteArrayView s) override
The ContentJobBase class.
KMime::Content * content() const
Get the resulting KMime::Content that the ContentJobBase has generated.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
KCODECS_EXPORT QByteArray base64Encode(QByteArrayView in)
QString path(const QString &relativePath)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromLatin1(QByteArrayView str)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:08:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.