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

KDE's Doxygen guidelines are available online.