summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-08-03 10:00:35 -0600
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-08-03 10:00:35 -0600
commitb7e18a461fd14ec34723d689f644880964314f1b (patch)
treef7ef6f625a987d8bd484ec283f4d1e5da38aa124
parentc71bc1b1fdd4055dfe2e2155961827b8652dd96c (diff)
downloadkube-b7e18a461fd14ec34723d689f644880964314f1b.tar.gz
kube-b7e18a461fd14ec34723d689f644880964314f1b.zip
Commit missing files
-rw-r--r--framework/src/CMakeLists.txt2
-rw-r--r--framework/src/domain/mime/mailcrypto.cpp444
-rw-r--r--framework/src/domain/mime/mailcrypto.h30
-rw-r--r--framework/src/domain/mime/mailtemplates.cpp5
-rw-r--r--framework/src/domain/mime/tests/mailtemplatetest.cpp30
5 files changed, 507 insertions, 4 deletions
diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt
index 85ad8344..8436705c 100644
--- a/framework/src/CMakeLists.txt
+++ b/framework/src/CMakeLists.txt
@@ -35,6 +35,7 @@ set(SRCS
35 domain/mime/attachmentmodel.cpp 35 domain/mime/attachmentmodel.cpp
36 domain/mime/partmodel.cpp 36 domain/mime/partmodel.cpp
37 domain/mime/mailtemplates.cpp 37 domain/mime/mailtemplates.cpp
38 domain/mime/mailcrypto.cpp
38 accounts/accountfactory.cpp 39 accounts/accountfactory.cpp
39 accounts/accountsmodel.cpp 40 accounts/accountsmodel.cpp
40 fabric.cpp 41 fabric.cpp
@@ -56,6 +57,7 @@ target_link_libraries(frameworkplugin
56 KF5::Contacts 57 KF5::Contacts
57 KF5::Package 58 KF5::Package
58 KAsync 59 KAsync
60 QGpgme
59) 61)
60install(TARGETS frameworkplugin DESTINATION ${FRAMEWORK_INSTALL_DIR}) 62install(TARGETS frameworkplugin DESTINATION ${FRAMEWORK_INSTALL_DIR})
61 63
diff --git a/framework/src/domain/mime/mailcrypto.cpp b/framework/src/domain/mime/mailcrypto.cpp
new file mode 100644
index 00000000..a5565b7d
--- /dev/null
+++ b/framework/src/domain/mime/mailcrypto.cpp
@@ -0,0 +1,444 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#include "mailcrypto.h"
20#include <QGpgME/Protocol>
21#include <QGpgME/SignJob>
22#include <gpgme++/global.h>
23#include <gpgme++/signingresult.h>
24#include <QDebug>
25
26/*
27 * Creating an ecrypted mail:
28 * * get keys (email -> fingreprint -> key)
29 * * Use Kleo::OpenPGPMIMEFormat,
30 *
31 */
32
33// bool chooseCTE()
34// {
35// Q_Q(SinglepartJob);
36
37// auto allowed = KMime::encodingsForData(data);
38
39// if (!q->globalPart()->is8BitAllowed()) {
40// allowed.removeAll(KMime::Headers::CE8Bit);
41// }
42
43// #if 0 //TODO signing
44// // In the following cases only QP and Base64 are allowed:
45// // - the buffer will be OpenPGP/MIME signed and it contains trailing
46// // whitespace (cf. RFC 3156)
47// // - a line starts with "From "
48// if ((willBeSigned && cf.hasTrailingWhitespace()) ||
49// cf.hasLeadingFrom()) {
50// ret.removeAll(DwMime::kCte8bit);
51// ret.removeAll(DwMime::kCte7bit);
52// }
53// #endif
54
55// if (contentTransferEncoding) {
56// // Specific CTE set. Check that our data fits in it.
57// if (!allowed.contains(contentTransferEncoding->encoding())) {
58// q->setError(JobBase::BugError);
59// q->setErrorText(i18n("%1 Content-Transfer-Encoding cannot correctly encode this message.",
60// KMime::nameForEncoding(contentTransferEncoding->encoding())));
61// return false;
62// // TODO improve error message in case 8bit is requested but not allowed.
63// }
64// } else {
65// // No specific CTE set. Choose the best one.
66// Q_ASSERT(!allowed.isEmpty());
67// contentTransferEncoding = new KMime::Headers::ContentTransferEncoding;
68// contentTransferEncoding->setEncoding(allowed.first());
69// }
70// qCDebug(MESSAGECOMPOSER_LOG) << "Settled on encoding" << KMime::nameForEncoding(contentTransferEncoding->encoding());
71// return true;
72// }
73
74KMime::Content *createPart(const QByteArray &encodedBody, const QByteArray &mimeType, const QByteArray &charset)
75{
76 auto resultContent = new KMime::Content;
77
78 auto contentType = new KMime::Headers::ContentType;
79 contentType->setMimeType(mimeType);
80 contentType->setMimeType(charset);
81 // if (!chooseCTE()) {
82 // Q_ASSERT(error());
83 // emitResult();
84 // return;
85 // }
86
87 // Set headers.
88 // if (contentDescription) {
89 // resultContent->setHeader(contentDescription);
90 // }
91 // if (contentDisposition) {
92 // resultContent->setHeader(contentDisposition);
93 // }
94 // if (contentID) {
95 // resultContent->setHeader(contentID);
96 // }
97 // Q_ASSERT(contentTransferEncoding); // chooseCTE() created it if it didn't exist.
98 auto contentTransferEncoding = new KMime::Headers::ContentTransferEncoding;
99 auto allowed = KMime::encodingsForData(encodedBody);
100 Q_ASSERT(!allowed.isEmpty());
101 contentTransferEncoding->setEncoding(allowed.first());
102 resultContent->setHeader(contentTransferEncoding);
103
104 if (contentType) {
105 resultContent->setHeader(contentType);
106 }
107
108 // Set data.
109 resultContent->setBody(encodedBody);
110 return resultContent;
111}
112
113KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentType *contentType, KMime::Content *ret)
114{
115 // MessageComposer::Composer composer;
116 // MessageComposer::SinglepartJob cteJob(&composer);
117 auto part = createPart(encodedBody, contentType->mimeType(), contentType->charset());
118 part->assemble();
119
120 // cteJob.contentType()->setMimeType(contentType->mimeType());
121 // cteJob.contentType()->setCharset(contentType->charset());
122 // cteJob.setData(encodedBody);
123 // cteJob.exec();
124 // cteJob.content()->assemble();
125
126 ret->contentTransferEncoding()->setEncoding(part->contentTransferEncoding()->encoding());
127 ret->setBody(part->encodedBody());
128
129 return ret;
130}
131
132void makeToplevelContentType(KMime::Content *content, bool sign, const QByteArray &hashAlgo)
133{
134 //Kleo::CryptoMessageFormat format,
135 // switch (format) {
136 // default:
137 // case Kleo::InlineOpenPGPFormat:
138 // case Kleo::OpenPGPMIMEFormat:
139 if (sign) {
140 content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
141 content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));
142 content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(QByteArray(QByteArrayLiteral("pgp-") + hashAlgo)).toLower());
143
144 } else {
145 content->contentType()->setMimeType(QByteArrayLiteral("multipart/encrypted"));
146 content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
147 }
148 return;
149 // case Kleo::SMIMEFormat:
150 // if (sign) {
151 // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME";
152 // content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
153 // content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromAscii("application/pkcs7-signature"));
154 // content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromAscii(hashAlgo).toLower());
155 // return;
156 // }
157 // // fall through (for encryption, there's no difference between
158 // // SMIME and SMIMEOpaque, since there is no mp/encrypted for
159 // // S/MIME)
160 // case Kleo::SMIMEOpaqueFormat:
161
162 // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME/opaque";
163 // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-mime"));
164
165 // if (sign) {
166 // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("signed-data"));
167 // } else {
168 // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("enveloped-data"));
169 // }
170 // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7m"));
171 // }
172}
173
174void setNestedContentType(KMime::Content *content, bool sign)
175{
176// , Kleo::CryptoMessageFormat format
177 // switch (format) {
178 // case Kleo::OpenPGPMIMEFormat:
179 if (sign) {
180 content->contentType()->setMimeType(QByteArrayLiteral("application/pgp-signature"));
181 content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("signature.asc"));
182 content->contentDescription()->from7BitString("This is a digitally signed message part.");
183 } else {
184 content->contentType()->setMimeType(QByteArrayLiteral("application/octet-stream"));
185 }
186 return;
187 // case Kleo::SMIMEFormat:
188 // if (sign) {
189 // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-signature"));
190 // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7s"));
191 // return;
192 // }
193 // // fall through:
194 // default:
195 // case Kleo::InlineOpenPGPFormat:
196 // case Kleo::SMIMEOpaqueFormat:
197 // ;
198 // }
199}
200
201void setNestedContentDisposition(KMime::Content *content, bool sign)
202{
203// Kleo::CryptoMessageFormat format,
204 // if (!sign && format & Kleo::OpenPGPMIMEFormat) {
205 if (!sign) {
206 content->contentDisposition()->setDisposition(KMime::Headers::CDinline);
207 content->contentDisposition()->setFilename(QStringLiteral("msg.asc"));
208 // } else if (sign && format & Kleo::SMIMEFormat) {
209 // content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
210 // content->contentDisposition()->setFilename(QStringLiteral("smime.p7s"));
211 }
212}
213
214// bool MessageComposer::Util::makeMultiMime(Kleo::CryptoMessageFormat format, bool sign)
215// {
216// switch (format) {
217// default:
218// case Kleo::InlineOpenPGPFormat:
219// case Kleo::SMIMEOpaqueFormat: return false;
220// case Kleo::OpenPGPMIMEFormat: return true;
221// case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME
222// }
223// }
224
225KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBody, bool sign, const QByteArray &hashAlgo)
226{
227 // Kleo::CryptoMessageFormat format,
228 KMime::Content *result = new KMime::Content;
229
230 // called should have tested that the signing/encryption failed
231 Q_ASSERT(!encodedBody.isEmpty());
232
233 // if (!(format & Kleo::InlineOpenPGPFormat)) { // make a MIME message
234 // qDebug() << "making MIME message, format:" << format;
235 makeToplevelContentType(result, sign, hashAlgo);
236
237 // if (makeMultiMime(sign)) { // sign/enc PGPMime, sign SMIME
238 if (true) { // sign/enc PGPMime, sign SMIME
239
240 const QByteArray boundary = KMime::multiPartBoundary();
241 result->contentType()->setBoundary(boundary);
242
243 result->assemble();
244 //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
245
246 // Build the encapsulated MIME parts.
247 // Build a MIME part holding the code information
248 // taking the body contents returned in ciphertext.
249 KMime::Content *code = new KMime::Content;
250 setNestedContentType(code, sign);
251 setNestedContentDisposition(code, sign);
252
253 if (sign) { // sign PGPMime, sign SMIME
254 // if (format & Kleo::AnySMIME) { // sign SMIME
255 // code->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
256 // code->contentTransferEncoding()->needToEncode();
257 // code->setBody(encodedBody);
258 // } else { // sign PGPMmime
259 setBodyAndCTE(encodedBody, orig->contentType(), code);
260 // }
261 result->addContent(orig);
262 result->addContent(code);
263 } else { // enc PGPMime
264 setBodyAndCTE(encodedBody, orig->contentType(), code);
265
266 // Build a MIME part holding the version information
267 // taking the body contents returned in
268 // structuring.data.bodyTextVersion.
269 KMime::Content *vers = new KMime::Content;
270 vers->contentType()->setMimeType("application/pgp-encrypted");
271 vers->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
272 vers->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
273 vers->setBody("Version: 1");
274
275 result->addContent(vers);
276 result->addContent(code);
277 }
278 } else { //enc SMIME, sign/enc SMIMEOpaque
279 result->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
280 result->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
281 result->contentDisposition()->setFilename(QStringLiteral("smime.p7m"));
282
283 result->assemble();
284 //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
285
286 result->setBody(encodedBody);
287 }
288 // } else { // sign/enc PGPInline
289 // result->setHead(orig->head());
290 // result->parse();
291
292 // // fixing ContentTransferEncoding
293 // setBodyAndCTE(encodedBody, orig->contentType(), result);
294 // }
295 result->assemble();
296 return result;
297}
298
299// bool binaryHint(Kleo::CryptoMessageFormat f)
300// {
301// switch (f) {
302// case Kleo::SMIMEFormat:
303// case Kleo::SMIMEOpaqueFormat:
304// return true;
305// default:
306// case Kleo::OpenPGPMIMEFormat:
307// case Kleo::InlineOpenPGPFormat:
308// return false;
309// }
310// }
311//
312 // GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
313 // {
314 // switch (f) {
315 // case Kleo::SMIMEOpaqueFormat:
316 // return GpgME::NormalSignatureMode;
317 // case Kleo::InlineOpenPGPFormat:
318 // return GpgME::Clearsigned;
319 // default:
320 // case Kleo::SMIMEFormat:
321 // case Kleo::OpenPGPMIMEFormat:
322 // return GpgME::Detached;
323 // }
324 // }
325
326//Hardcoded OpenPGPGMIMEFormat for now
327KMime::Content *MailCrypto::sign(KMime::Content *content, const std::vector<GpgME::Key> &signers)
328{
329
330 // if setContent hasn't been called, we assume that a subjob was added
331 // and we want to use that
332 // if (!d->content) {
333 // Q_ASSERT(d->subjobContents.size() == 1);
334 // d->content = d->subjobContents.first();
335 // }
336
337 //d->resultContent = new KMime::Content;
338
339 // const QGpgME::Protocol *proto = nullptr;
340 // if (d->format & Kleo::AnyOpenPGP) {
341 // proto = QGpgME::openpgp();
342 // } else if (d->format & Kleo::AnySMIME) {
343 // proto = QGpgME::smime();
344 // }
345
346 const QGpgME::Protocol *proto = QGpgME::openpgp();
347 Q_ASSERT(proto);
348
349 qDebug() << "creating signJob from:" << proto->name() << proto->displayName();
350 // std::unique_ptr<QGpgME::SignJob> job(proto->signJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
351 bool armor = true;
352 bool textMode = false;
353 std::unique_ptr<QGpgME::SignJob> job(proto->signJob(armor, textMode));
354 // for now just do the main recipients
355 QByteArray signature;
356
357 content->assemble();
358
359 // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
360 // according to RfC 2633, 3.1.1 Canonicalization
361 QByteArray contentData;
362 // if (d->format & Kleo::InlineOpenPGPFormat) {
363 // content = d->content->body();
364 // } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
365
366 // replace "From " and "--" at the beginning of lines
367 // with encoded versions according to RfC 3156, 3
368 // Note: If any line begins with the string "From ", it is strongly
369 // suggested that either the Quoted-Printable or Base64 MIME encoding
370 // be applied.
371 const auto encoding = content->contentTransferEncoding()->encoding();
372 if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit)
373 && !content->contentType(false)) {
374 QByteArray body = content->encodedBody();
375 bool changed = false;
376 QList<QByteArray> search;
377 QList<QByteArray> replacements;
378
379 search << "From "
380 << "from "
381 << "-";
382 replacements << "From=20"
383 << "from=20"
384 << "=2D";
385
386 if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
387 for (int i = 0; i < search.size(); ++i) {
388 const auto pos = body.indexOf(search[i]);
389 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
390 changed = true;
391 break;
392 }
393 }
394 if (changed) {
395 content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
396 content->assemble();
397 body = content->encodedBody();
398 }
399 }
400
401 for (int i = 0; i < search.size(); ++i) {
402 const auto pos = body.indexOf(search[i]);
403 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
404 changed = true;
405 body.replace(pos, search[i].size(), replacements[i]);
406 }
407 }
408
409 if (changed) {
410 qDebug() << "Content changed";
411 content->setBody(body);
412 content->contentTransferEncoding()->setDecoded(false);
413 }
414 }
415
416 contentData = KMime::LFtoCRLF(content->encodedContent());
417 // } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
418 // contentData = content->encodedContent();
419 // }
420
421 auto signingMode = GpgME::Detached;
422
423 // FIXME: Make this async
424 GpgME::SigningResult res = job->exec(signers,
425 contentData,
426 signingMode,
427 signature);
428
429 // exec'ed jobs don't delete themselves
430 job->deleteLater();
431
432 if (res.error().code()) {
433 qWarning() << "signing failed:" << res.error().asString();
434 // job->showErrorDialog( globalPart()->parentWidgetForGui() );
435 // setError(res.error().code());
436 // setErrorText(QString::fromLocal8Bit(res.error().asString()));
437 } else {
438 QByteArray signatureHashAlgo = res.createdSignature(0).hashAlgorithmAsString();
439 bool sign = true;
440 return composeHeadersAndBody(content, signature, sign, signatureHashAlgo);
441 }
442 return nullptr;
443}
444
diff --git a/framework/src/domain/mime/mailcrypto.h b/framework/src/domain/mime/mailcrypto.h
new file mode 100644
index 00000000..2261182d
--- /dev/null
+++ b/framework/src/domain/mime/mailcrypto.h
@@ -0,0 +1,30 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#pragma once
21
22#include <QByteArray>
23#include <KMime/Message>
24#include <gpgme++/key.h>
25#include <functional>
26
27namespace MailCrypto
28{
29 KMime::Content *sign(KMime::Content *content, const std::vector<GpgME::Key> &signers);
30};
diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp
index dc713ee4..0380fa5c 100644
--- a/framework/src/domain/mime/mailtemplates.cpp
+++ b/framework/src/domain/mime/mailtemplates.cpp
@@ -977,11 +977,8 @@ KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMes
977 for (const auto &attachment : attachments) { 977 for (const auto &attachment : attachments) {
978 mail->addContent(createAttachmentPart(attachment.data, attachment.filename, attachment.isInline, attachment.mimeType, attachment.name)); 978 mail->addContent(createAttachmentPart(attachment.data, attachment.filename, attachment.isInline, attachment.mimeType, attachment.name));
979 } 979 }
980 bodyPart = createBodyPart(body.toUtf8());
981 } else {
982 //FIXME same implementation as above for attachments
983 bodyPart = createBodyPart(body.toUtf8());
984 } 980 }
981 bodyPart = createBodyPart(body.toUtf8());
985 mail->assemble(); 982 mail->assemble();
986 983
987 KMime::Content *signedResult = nullptr; 984 KMime::Content *signedResult = nullptr;
diff --git a/framework/src/domain/mime/tests/mailtemplatetest.cpp b/framework/src/domain/mime/tests/mailtemplatetest.cpp
index 62b17a6c..e814f75f 100644
--- a/framework/src/domain/mime/tests/mailtemplatetest.cpp
+++ b/framework/src/domain/mime/tests/mailtemplatetest.cpp
@@ -214,6 +214,36 @@ private slots:
214 qWarning() << "---------------------------------"; 214 qWarning() << "---------------------------------";
215 QCOMPARE(result->subject()->asUnicodeString(), subject); 215 QCOMPARE(result->subject()->asUnicodeString(), subject);
216 QVERIFY(result->contentType()->isMimeType("multipart/signed")); 216 QVERIFY(result->contentType()->isMimeType("multipart/signed"));
217
218 const auto contents = result->contents();
219 QCOMPARE(contents.size(), 2);
220 {
221 auto c = contents.at(0);
222 QVERIFY(c->contentType()->isMimeType("text/plain"));
223 }
224 {
225 auto c = contents.at(1);
226 QVERIFY(c->contentType()->isMimeType("application/pgp-signature"));
227 }
228 }
229
230 void testCreatePlainMailWithAttachmentsSigned()
231 {
232 QStringList to = {{"to@example.org"}};
233 QStringList cc = {{"cc@example.org"}};;
234 QStringList bcc = {{"bcc@example.org"}};;
235 KMime::Types::Mailbox from;
236 from.fromUnicodeString("from@example.org");
237 QString subject = "subject";
238 QString body = "body";
239 QList<Attachment> attachments = {{"name", "filename", "mimetype", true, "inlineAttachment"}, {"name", "filename", "mimetype", false, "nonInlineAttachment"}};
240
241 std::vector<GpgME::Key> keys = getKeys();
242
243 auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, attachments, keys);
244
245 QVERIFY(result);
246 QVERIFY(result->contentType()->isMimeType("multipart/signed"));
217 } 247 }
218}; 248};
219 249