Messagelib

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

KDE's Doxygen guidelines are available online.