diff options
-rw-r--r-- | framework/src/domain/composercontroller.cpp | 11 | ||||
-rw-r--r-- | framework/src/domain/mime/mailcrypto.cpp | 568 | ||||
-rw-r--r-- | framework/src/domain/mime/mailcrypto.h | 3 | ||||
-rw-r--r-- | framework/src/domain/mime/mailtemplates.cpp | 9 | ||||
-rw-r--r-- | framework/src/domain/mime/mailtemplates.h | 2 | ||||
-rw-r--r-- | framework/src/errors.h | 46 |
6 files changed, 192 insertions, 447 deletions
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp index 2286a71b..37902164 100644 --- a/framework/src/domain/composercontroller.cpp +++ b/framework/src/domain/composercontroller.cpp | |||
@@ -463,18 +463,25 @@ KMime::Message::Ptr ComposerController::assembleMessage() | |||
463 | }; | 463 | }; |
464 | }); | 464 | }); |
465 | 465 | ||
466 | GpgME::Key attachedKey; | ||
466 | std::vector<GpgME::Key> signingKeys; | 467 | std::vector<GpgME::Key> signingKeys; |
467 | if (getSign()) { | 468 | if (getSign()) { |
468 | signingKeys = getPersonalKeys().value<std::vector<GpgME::Key>>(); | 469 | signingKeys = getPersonalKeys().value<std::vector<GpgME::Key>>(); |
470 | Q_ASSERT(!signingKeys.empty()); | ||
471 | attachedKey = signingKeys[0]; | ||
469 | } | 472 | } |
470 | std::vector<GpgME::Key> encryptionKeys; | 473 | std::vector<GpgME::Key> encryptionKeys; |
471 | if (getEncrypt()) { | 474 | if (getEncrypt()) { |
472 | //Encrypt to self so we can read the sent message | 475 | //Encrypt to self so we can read the sent message |
473 | encryptionKeys += getPersonalKeys().value<std::vector<GpgME::Key>>(); | 476 | auto personalKeys = getPersonalKeys().value<std::vector<GpgME::Key>>(); |
477 | |||
478 | attachedKey = personalKeys[0]; | ||
479 | |||
480 | encryptionKeys += personalKeys; | ||
474 | encryptionKeys += getRecipientKeys(); | 481 | encryptionKeys += getRecipientKeys(); |
475 | } | 482 | } |
476 | 483 | ||
477 | return MailTemplates::createMessage(mExistingMessage, toAddresses, ccAddresses, bccAddresses, getIdentity(), getSubject(), getBody(), getHtmlBody(), attachments, signingKeys, encryptionKeys); | 484 | return MailTemplates::createMessage(mExistingMessage, toAddresses, ccAddresses, bccAddresses, getIdentity(), getSubject(), getBody(), getHtmlBody(), attachments, signingKeys, encryptionKeys, attachedKey); |
478 | } | 485 | } |
479 | 486 | ||
480 | void ComposerController::send() | 487 | void ComposerController::send() |
diff --git a/framework/src/domain/mime/mailcrypto.cpp b/framework/src/domain/mime/mailcrypto.cpp index 4a26829f..f25c75fe 100644 --- a/framework/src/domain/mime/mailcrypto.cpp +++ b/framework/src/domain/mime/mailcrypto.cpp | |||
@@ -21,6 +21,8 @@ | |||
21 | */ | 21 | */ |
22 | #include "mailcrypto.h" | 22 | #include "mailcrypto.h" |
23 | 23 | ||
24 | #include "framework/src/errors.h" | ||
25 | |||
24 | #include <QGpgME/DataProvider> | 26 | #include <QGpgME/DataProvider> |
25 | #include <QGpgME/EncryptJob> | 27 | #include <QGpgME/EncryptJob> |
26 | #include <QGpgME/ExportJob> | 28 | #include <QGpgME/ExportJob> |
@@ -41,119 +43,6 @@ | |||
41 | #include <future> | 43 | #include <future> |
42 | #include <utility> | 44 | #include <utility> |
43 | 45 | ||
44 | /* | ||
45 | * FIXME: | ||
46 | * | ||
47 | * This code is WIP. | ||
48 | * It currently only implements OpenPGPMIMEFormat for signing. | ||
49 | * All the commented code are intentional leftovers that we can clean-up | ||
50 | * once all necessary signing mechanisms have been implemented. | ||
51 | * | ||
52 | * Creating an ecrypted mail: | ||
53 | * * get keys (email -> fingreprint -> key) | ||
54 | * * Use Kleo::OpenPGPMIMEFormat, | ||
55 | * | ||
56 | */ | ||
57 | |||
58 | // bool chooseCTE() | ||
59 | // { | ||
60 | // Q_Q(SinglepartJob); | ||
61 | |||
62 | // auto allowed = KMime::encodingsForData(data); | ||
63 | |||
64 | // if (!q->globalPart()->is8BitAllowed()) { | ||
65 | // allowed.removeAll(KMime::Headers::CE8Bit); | ||
66 | // } | ||
67 | |||
68 | // #if 0 //TODO signing | ||
69 | // // In the following cases only QP and Base64 are allowed: | ||
70 | // // - the buffer will be OpenPGP/MIME signed and it contains trailing | ||
71 | // // whitespace (cf. RFC 3156) | ||
72 | // // - a line starts with "From " | ||
73 | // if ((willBeSigned && cf.hasTrailingWhitespace()) || | ||
74 | // cf.hasLeadingFrom()) { | ||
75 | // ret.removeAll(DwMime::kCte8bit); | ||
76 | // ret.removeAll(DwMime::kCte7bit); | ||
77 | // } | ||
78 | // #endif | ||
79 | |||
80 | // if (contentTransferEncoding) { | ||
81 | // // Specific CTE set. Check that our data fits in it. | ||
82 | // if (!allowed.contains(contentTransferEncoding->encoding())) { | ||
83 | // q->setError(JobBase::BugError); | ||
84 | // q->setErrorText(i18n("%1 Content-Transfer-Encoding cannot correctly encode this message.", | ||
85 | // KMime::nameForEncoding(contentTransferEncoding->encoding()))); | ||
86 | // return false; | ||
87 | // // TODO improve error message in case 8bit is requested but not allowed. | ||
88 | // } | ||
89 | // } else { | ||
90 | // // No specific CTE set. Choose the best one. | ||
91 | // Q_ASSERT(!allowed.isEmpty()); | ||
92 | // contentTransferEncoding = new KMime::Headers::ContentTransferEncoding; | ||
93 | // contentTransferEncoding->setEncoding(allowed.first()); | ||
94 | // } | ||
95 | // qCDebug(MESSAGECOMPOSER_LOG) << "Settled on encoding" << KMime::nameForEncoding(contentTransferEncoding->encoding()); | ||
96 | // return true; | ||
97 | // } | ||
98 | |||
99 | KMime::Content *createPart(const QByteArray &encodedBody, const QByteArray &mimeType, const QByteArray &charset) | ||
100 | { | ||
101 | auto resultContent = new KMime::Content; | ||
102 | |||
103 | auto contentType = new KMime::Headers::ContentType; | ||
104 | contentType->setMimeType(mimeType); | ||
105 | contentType->setCharset(charset); | ||
106 | // if (!chooseCTE()) { | ||
107 | // Q_ASSERT(error()); | ||
108 | // emitResult(); | ||
109 | // return; | ||
110 | // } | ||
111 | |||
112 | // Set headers. | ||
113 | // if (contentDescription) { | ||
114 | // resultContent->setHeader(contentDescription); | ||
115 | // } | ||
116 | // if (contentDisposition) { | ||
117 | // resultContent->setHeader(contentDisposition); | ||
118 | // } | ||
119 | // if (contentID) { | ||
120 | // resultContent->setHeader(contentID); | ||
121 | // } | ||
122 | // Q_ASSERT(contentTransferEncoding); // chooseCTE() created it if it didn't exist. | ||
123 | auto contentTransferEncoding = new KMime::Headers::ContentTransferEncoding; | ||
124 | auto allowed = KMime::encodingsForData(encodedBody); | ||
125 | Q_ASSERT(!allowed.isEmpty()); | ||
126 | contentTransferEncoding->setEncoding(allowed.first()); | ||
127 | resultContent->setHeader(contentTransferEncoding); | ||
128 | |||
129 | if (contentType) { | ||
130 | resultContent->setHeader(contentType); | ||
131 | } | ||
132 | |||
133 | // Set data. | ||
134 | resultContent->setBody(encodedBody); | ||
135 | return resultContent; | ||
136 | } | ||
137 | |||
138 | KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentType *contentType, KMime::Content *ret) | ||
139 | { | ||
140 | // MessageComposer::Composer composer; | ||
141 | // MessageComposer::SinglepartJob cteJob(&composer); | ||
142 | auto part = createPart(encodedBody, contentType->mimeType(), contentType->charset()); | ||
143 | part->assemble(); | ||
144 | |||
145 | // cteJob.contentType()->setMimeType(contentType->mimeType()); | ||
146 | // cteJob.contentType()->setCharset(contentType->charset()); | ||
147 | // cteJob.setData(encodedBody); | ||
148 | // cteJob.exec(); | ||
149 | // cteJob.content()->assemble(); | ||
150 | |||
151 | ret->contentTransferEncoding()->setEncoding(part->contentTransferEncoding()->encoding()); | ||
152 | ret->setBody(part->encodedBody()); | ||
153 | |||
154 | return ret; | ||
155 | } | ||
156 | |||
157 | // replace simple LFs by CRLFs for all MIME supporting CryptPlugs | 46 | // replace simple LFs by CRLFs for all MIME supporting CryptPlugs |
158 | // according to RfC 2633, 3.1.1 Canonicalization | 47 | // according to RfC 2633, 3.1.1 Canonicalization |
159 | static QByteArray canonicalizeContent(KMime::Content *content) | 48 | static QByteArray canonicalizeContent(KMime::Content *content) |
@@ -220,44 +109,60 @@ static QByteArray canonicalizeContent(KMime::Content *content) | |||
220 | } | 109 | } |
221 | 110 | ||
222 | /** | 111 | /** |
112 | * Get the given `key` in the armor format. | ||
113 | */ | ||
114 | Expected<GpgME::Error, QByteArray> exportPublicKey(const GpgME::Key &key) | ||
115 | { | ||
116 | // Not using the Qt API because it apparently blocks (the `result` signal is never | ||
117 | // triggered) | ||
118 | std::unique_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP)); | ||
119 | ctx->setArmor(true); | ||
120 | |||
121 | QGpgME::QByteArrayDataProvider dp; | ||
122 | GpgME::Data data(&dp); | ||
123 | |||
124 | qDebug() << "Exporting public key:" << key.shortKeyID(); | ||
125 | auto error = ctx->exportPublicKeys(key.keyID(), data); | ||
126 | |||
127 | if (error.code()) { | ||
128 | return makeUnexpected(error); | ||
129 | } | ||
130 | |||
131 | return dp.data(); | ||
132 | } | ||
133 | |||
134 | /** | ||
223 | * Create an Email with `msg` as a body and `key` as an attachment. | 135 | * Create an Email with `msg` as a body and `key` as an attachment. |
224 | * | 136 | * |
137 | * Will create the given structure: | ||
138 | * | ||
139 | * + `multipart/mixed` | ||
140 | * - the given `msg` | ||
141 | * - `application/pgp-keys` (the given `key` as attachment) | ||
142 | * | ||
225 | * Used by the `createSignedEmail` and `createEncryptedEmail` functions. | 143 | * Used by the `createSignedEmail` and `createEncryptedEmail` functions. |
226 | */ | 144 | */ |
227 | KMime::Content *setMessageAndPublicKey(KMime::Content *msg, const GpgME::Key &key) | 145 | Expected<GpgME::Error, KMime::Content *> appendPublicKey(KMime::Content *msg, const GpgME::Key &key) |
228 | { | 146 | { |
147 | const auto publicKeyExportResult = exportPublicKey(key); | ||
148 | |||
149 | if(!publicKeyExportResult) { | ||
150 | // "Could not export public key" | ||
151 | return makeUnexpected(publicKeyExportResult.error()); | ||
152 | } | ||
153 | |||
154 | const auto publicKeyData = publicKeyExportResult.value(); | ||
155 | |||
229 | auto result = new KMime::Content; | 156 | auto result = new KMime::Content; |
230 | result->contentType()->setMimeType("multipart/mixed"); | 157 | result->contentType()->setMimeType("multipart/mixed"); |
231 | result->contentType()->setBoundary(KMime::multiPartBoundary()); | 158 | result->contentType()->setBoundary(KMime::multiPartBoundary()); |
232 | 159 | ||
233 | KMime::Content *keyAttachment = new KMime::Content; | 160 | KMime::Content *keyAttachment = new KMime::Content; |
234 | { | 161 | { |
235 | // Not using the Qt API because it apparently blocks (the `result` signal is never | ||
236 | // triggered) | ||
237 | std::unique_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP)); | ||
238 | ctx->setArmor(true); | ||
239 | |||
240 | qDebug() << "Setting up data container"; | ||
241 | QGpgME::QByteArrayDataProvider dp; | ||
242 | GpgME::Data data(&dp); | ||
243 | |||
244 | qDebug() << "Exporting public key"; | ||
245 | auto error = ctx->exportPublicKeys(key.keyID(), data); | ||
246 | |||
247 | if (error.code()) { | ||
248 | qWarning() << "Could not export public key:" << error.asString(); | ||
249 | // TODO: XXX: handle errors better | ||
250 | // throw std::runtime_error("Export public key failed"); | ||
251 | delete result; | ||
252 | return msg; | ||
253 | } | ||
254 | |||
255 | keyAttachment->contentType()->setMimeType("application/pgp-keys"); | 162 | keyAttachment->contentType()->setMimeType("application/pgp-keys"); |
256 | keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment); | 163 | keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment); |
257 | keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc"); | 164 | keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc"); |
258 | qDebug() << "Getting data"; | 165 | keyAttachment->setBody(publicKeyData); |
259 | qWarning() << "Data:" << dp.data().constData(); | ||
260 | keyAttachment->setBody(dp.data()); | ||
261 | } | 166 | } |
262 | 167 | ||
263 | msg->assemble(); | 168 | msg->assemble(); |
@@ -272,7 +177,7 @@ KMime::Content *setMessageAndPublicKey(KMime::Content *msg, const GpgME::Key &ke | |||
272 | return result; | 177 | return result; |
273 | } | 178 | } |
274 | 179 | ||
275 | QByteArray encrypt(const QByteArray &content, const std::vector<GpgME::Key> &encryptionKeys) | 180 | Expected<GpgME::Error, QByteArray> encrypt(const QByteArray &content, const std::vector<GpgME::Key> &encryptionKeys) |
276 | { | 181 | { |
277 | QByteArray resultData; | 182 | QByteArray resultData; |
278 | 183 | ||
@@ -282,15 +187,13 @@ QByteArray encrypt(const QByteArray &content, const std::vector<GpgME::Key> &enc | |||
282 | 187 | ||
283 | if (result.error().code()) { | 188 | if (result.error().code()) { |
284 | qWarning() << "Encryption failed:" << result.error().asString(); | 189 | qWarning() << "Encryption failed:" << result.error().asString(); |
285 | // TODO: XXX: handle errors better | 190 | return makeUnexpected(result.error()); |
286 | // throw std::runtime_error("Signing failed"); | ||
287 | return ""; | ||
288 | } | 191 | } |
289 | 192 | ||
290 | return resultData; | 193 | return resultData; |
291 | } | 194 | } |
292 | 195 | ||
293 | QByteArray signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys, | 196 | Expected<GpgME::Error, QByteArray> signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys, |
294 | const std::vector<GpgME::Key> &encryptionKeys) | 197 | const std::vector<GpgME::Key> &encryptionKeys) |
295 | { | 198 | { |
296 | QByteArray resultData; | 199 | QByteArray resultData; |
@@ -301,26 +204,29 @@ QByteArray signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Ke | |||
301 | 204 | ||
302 | if (result.first.error().code()) { | 205 | if (result.first.error().code()) { |
303 | qWarning() << "Signing failed:" << result.first.error().asString(); | 206 | qWarning() << "Signing failed:" << result.first.error().asString(); |
304 | // TODO: XXX: handle errors better | 207 | return makeUnexpected(result.first.error()); |
305 | // throw std::runtime_error("Signing failed"); | ||
306 | return ""; | ||
307 | } | 208 | } |
308 | 209 | ||
309 | if (result.second.error().code()) { | 210 | if (result.second.error().code()) { |
310 | qWarning() << "Encryption failed:" << result.second.error().asString(); | 211 | qWarning() << "Encryption failed:" << result.second.error().asString(); |
311 | // TODO: XXX: handle errors better | 212 | return makeUnexpected(result.second.error()); |
312 | // throw std::runtime_error("Encryption failed"); | ||
313 | return ""; | ||
314 | } | 213 | } |
315 | 214 | ||
316 | return resultData; | 215 | return resultData; |
317 | } | 216 | } |
318 | 217 | ||
319 | // Create a message like this (according to RFC 3156 Section 4): | 218 | /** |
320 | // | 219 | * Create a message like this (according to RFC 3156 Section 4): |
321 | // - multipart/encrypted | 220 | * |
322 | // - application/pgp-encrypted (version information) | 221 | * - multipart/encrypted |
323 | // - application/octet-stream (given encrypted data) | 222 | * - application/pgp-encrypted (version information) |
223 | * - application/octet-stream (given encrypted data) | ||
224 | * | ||
225 | * Should not be used directly since the public key should be attached, hence | ||
226 | * the `createEncryptedEmail` function. | ||
227 | * | ||
228 | * The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions. | ||
229 | */ | ||
324 | KMime::Content *createEncryptedPart(QByteArray encryptedData) | 230 | KMime::Content *createEncryptedPart(QByteArray encryptedData) |
325 | { | 231 | { |
326 | KMime::Content *result = new KMime::Content; | 232 | KMime::Content *result = new KMime::Content; |
@@ -358,20 +264,47 @@ KMime::Content *createEncryptedPart(QByteArray encryptedData) | |||
358 | return result; | 264 | return result; |
359 | } | 265 | } |
360 | 266 | ||
361 | KMime::Content *createEncryptedEmail(KMime::Content *content, const std::vector<GpgME::Key> &encryptionKeys, | 267 | /** |
362 | bool sign, const std::vector<GpgME::Key> &signingKeys = {}) | 268 | * Create an encrypted (optionally signed) email with a public key attached to it. |
269 | * | ||
270 | * Will create a message like this: | ||
271 | * | ||
272 | * + `multipart/mixed` | ||
273 | * - `multipart/encrypted` | ||
274 | * + `application/pgp-encrypted | ||
275 | * + `application/octet-stream` (a generated encrypted version of the original message) | ||
276 | * - `application/pgp-keys` (the public key as attachment, which is the first of the | ||
277 | * `signingKeys`) | ||
278 | */ | ||
279 | Expected<GpgME::Error, KMime::Content *> | ||
280 | createEncryptedEmail(KMime::Content *content, const std::vector<GpgME::Key> &encryptionKeys, | ||
281 | const GpgME::Key &attachedKey, const std::vector<GpgME::Key> &signingKeys = {}) | ||
363 | { | 282 | { |
364 | auto contentToEncrypt = canonicalizeContent(content); | 283 | auto contentToEncrypt = canonicalizeContent(content); |
365 | 284 | ||
366 | auto encryptedData = sign ? encrypt(contentToEncrypt, encryptionKeys) : | 285 | auto encryptionResult = signingKeys.empty() ? |
286 | encrypt(contentToEncrypt, encryptionKeys) : | ||
367 | signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys); | 287 | signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys); |
288 | |||
289 | if (!encryptionResult) { | ||
290 | return makeUnexpected(encryptionResult.error()); | ||
291 | } | ||
292 | |||
293 | auto encryptedData = encryptionResult.value(); | ||
294 | |||
368 | KMime::Content *encryptedPart = createEncryptedPart(encryptedData); | 295 | KMime::Content *encryptedPart = createEncryptedPart(encryptedData); |
369 | 296 | ||
370 | // TODO: check signingKeys not empty | 297 | auto publicKeyAppendResult = appendPublicKey(encryptedPart, attachedKey); |
371 | return setMessageAndPublicKey(encryptedPart, signingKeys[0]); | 298 | |
299 | // TODO: this is ugly | ||
300 | if (!publicKeyAppendResult) { | ||
301 | delete encryptedPart; | ||
302 | } | ||
303 | |||
304 | return publicKeyAppendResult; | ||
372 | } | 305 | } |
373 | 306 | ||
374 | QByteArray sign(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys) | 307 | Expected<GpgME::Error, QByteArray> sign(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys) |
375 | { | 308 | { |
376 | QByteArray resultData; | 309 | QByteArray resultData; |
377 | 310 | ||
@@ -381,20 +314,25 @@ QByteArray sign(const QByteArray &content, const std::vector<GpgME::Key> &signin | |||
381 | 314 | ||
382 | if (result.error().code()) { | 315 | if (result.error().code()) { |
383 | qWarning() << "Signing failed:" << result.error().asString(); | 316 | qWarning() << "Signing failed:" << result.error().asString(); |
384 | // TODO: XXX: handle errors better | 317 | return makeUnexpected(result.error()); |
385 | // throw std::runtime_error("Signing failed"); | ||
386 | return ""; | ||
387 | } | 318 | } |
388 | 319 | ||
389 | return resultData; | 320 | return resultData; |
390 | } | 321 | } |
391 | 322 | ||
392 | // Create a message like this (according to RFC 3156 Section 5): | 323 | /** |
393 | // | 324 | * Create a message like this (according to RFC 3156 Section 5): |
394 | // - multipart/signed | 325 | * |
395 | // - whatever the given original message is (should be canonicalized) | 326 | * + `multipart/signed` |
396 | // - application/octet-stream (given encrypted data) | 327 | * - whatever the given original `message` is (should be canonicalized) |
397 | KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &signedData, const QString &micAlg) | 328 | * - `application/octet-stream` (the given `signature`) |
329 | * | ||
330 | * Should not be used directly since the public key should be attached, hence | ||
331 | * the `createSignedEmail` function. | ||
332 | * | ||
333 | * The signature can be generated by the `sign` function. | ||
334 | */ | ||
335 | KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &signature, const QString &micAlg) | ||
398 | { | 336 | { |
399 | KMime::Content *result = new KMime::Content; | 337 | KMime::Content *result = new KMime::Content; |
400 | 338 | ||
@@ -413,7 +351,7 @@ KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &sign | |||
413 | signedPartPart->contentDescription()->from7BitString( | 351 | signedPartPart->contentDescription()->from7BitString( |
414 | "This is a digitally signed message part"); | 352 | "This is a digitally signed message part"); |
415 | 353 | ||
416 | signedPartPart->setBody(signedData); | 354 | signedPartPart->setBody(signature); |
417 | 355 | ||
418 | result->addContent(signedPartPart); | 356 | result->addContent(signedPartPart); |
419 | } | 357 | } |
@@ -421,283 +359,64 @@ KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &sign | |||
421 | return result; | 359 | return result; |
422 | } | 360 | } |
423 | 361 | ||
424 | KMime::Content *createSignedEmail(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys) | 362 | /** |
363 | * Create a signed email with a public key attached to it. | ||
364 | * | ||
365 | * Will create a message like this: | ||
366 | * | ||
367 | * + `multipart/mixed` | ||
368 | * - `multipart/signed` | ||
369 | * + whatever the given original `content` is (should not be canonalized) | ||
370 | * + `application/octet-stream` (a generated signature of the original message) | ||
371 | * - `application/pgp-keys` (the public key as attachment, which is the first of the | ||
372 | * `signingKeys`) | ||
373 | */ | ||
374 | Expected<GpgME::Error, KMime::Content *> createSignedEmail(KMime::Content *content, | ||
375 | const std::vector<GpgME::Key> &signingKeys, const GpgME::Key &attachedKey) | ||
425 | { | 376 | { |
426 | auto contentToSign = canonicalizeContent(content); | 377 | Q_ASSERT(!signingKeys.empty()); |
427 | 378 | ||
428 | auto signedData = sign(contentToSign, signingKeys); | 379 | auto contentToSign = canonicalizeContent(content); |
429 | KMime::Content *signedPart = createSignedPart(content, signedData, "TODO: pgp-something"); | ||
430 | |||
431 | // TODO: check signingKeys not empty | ||
432 | return setMessageAndPublicKey(signedPart, signingKeys[0]); | ||
433 | } | ||
434 | |||
435 | void makeToplevelContentType(KMime::Content *content, bool encrypt, const QByteArray &hashAlgo) | ||
436 | { | ||
437 | //Kleo::CryptoMessageFormat format, | ||
438 | // switch (format) { | ||
439 | // default: | ||
440 | // case Kleo::InlineOpenPGPFormat: | ||
441 | // case Kleo::OpenPGPMIMEFormat: | ||
442 | if (encrypt) { | ||
443 | content->contentType()->setMimeType(QByteArrayLiteral("multipart/encrypted")); | ||
444 | content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted")); | ||
445 | } else { | ||
446 | content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed")); | ||
447 | content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature")); | ||
448 | content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(QByteArray(QByteArrayLiteral("pgp-") + hashAlgo)).toLower()); | ||
449 | } | ||
450 | return; | ||
451 | // case Kleo::SMIMEFormat: | ||
452 | // if (sign) { | ||
453 | // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME"; | ||
454 | // content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed")); | ||
455 | // content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromAscii("application/pkcs7-signature")); | ||
456 | // content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromAscii(hashAlgo).toLower()); | ||
457 | // return; | ||
458 | // } | ||
459 | // // fall through (for encryption, there's no difference between | ||
460 | // // SMIME and SMIMEOpaque, since there is no mp/encrypted for | ||
461 | // // S/MIME) | ||
462 | // case Kleo::SMIMEOpaqueFormat: | ||
463 | |||
464 | // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME/opaque"; | ||
465 | // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-mime")); | ||
466 | |||
467 | // if (sign) { | ||
468 | // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("signed-data")); | ||
469 | // } else { | ||
470 | // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("enveloped-data")); | ||
471 | // } | ||
472 | // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7m")); | ||
473 | // } | ||
474 | } | ||
475 | 380 | ||
476 | void setNestedContentType(KMime::Content *content, bool encrypt) | 381 | auto signingResult = sign(contentToSign, signingKeys); |
477 | { | ||
478 | // , Kleo::CryptoMessageFormat format | ||
479 | // switch (format) { | ||
480 | // case Kleo::OpenPGPMIMEFormat: | ||
481 | if (encrypt) { | ||
482 | content->contentType()->setMimeType(QByteArrayLiteral("application/octet-stream")); | ||
483 | } else { | ||
484 | content->contentType()->setMimeType(QByteArrayLiteral("application/pgp-signature")); | ||
485 | content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("signature.asc")); | ||
486 | content->contentDescription()->from7BitString("This is a digitally signed message part."); | ||
487 | } | ||
488 | return; | ||
489 | // case Kleo::SMIMEFormat: | ||
490 | // if (sign) { | ||
491 | // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-signature")); | ||
492 | // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7s")); | ||
493 | // return; | ||
494 | // } | ||
495 | // // fall through: | ||
496 | // default: | ||
497 | // case Kleo::InlineOpenPGPFormat: | ||
498 | // case Kleo::SMIMEOpaqueFormat: | ||
499 | // ; | ||
500 | // } | ||
501 | } | ||
502 | 382 | ||
503 | void setNestedContentDisposition(KMime::Content *content, bool encrypt) | 383 | if (!signingResult) { |
504 | { | 384 | return makeUnexpected(signingResult.error()); |
505 | // Kleo::CryptoMessageFormat format, | ||
506 | // if (!sign && format & Kleo::OpenPGPMIMEFormat) { | ||
507 | if (encrypt) { | ||
508 | content->contentDisposition()->setDisposition(KMime::Headers::CDinline); | ||
509 | content->contentDisposition()->setFilename(QStringLiteral("msg.asc")); | ||
510 | // } else if (sign && format & Kleo::SMIMEFormat) { | ||
511 | // content->contentDisposition()->setDisposition(KMime::Headers::CDattachment); | ||
512 | // content->contentDisposition()->setFilename(QStringLiteral("smime.p7s")); | ||
513 | } | 385 | } |
514 | } | ||
515 | 386 | ||
516 | // bool MessageComposer::Util::makeMultiMime(Kleo::CryptoMessageFormat format, bool sign) | 387 | auto signedData = signingResult.value(); |
517 | // { | ||
518 | // switch (format) { | ||
519 | // default: | ||
520 | // case Kleo::InlineOpenPGPFormat: | ||
521 | // case Kleo::SMIMEOpaqueFormat: return false; | ||
522 | // case Kleo::OpenPGPMIMEFormat: return true; | ||
523 | // case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME | ||
524 | // } | ||
525 | // } | ||
526 | |||
527 | KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBody, bool encrypt, const QByteArray &hashAlgo) | ||
528 | { | ||
529 | // Kleo::CryptoMessageFormat format, | ||
530 | KMime::Content *result = new KMime::Content; | ||
531 | 388 | ||
532 | // called should have tested that the signing/encryption failed | 389 | KMime::Content *signedPart = createSignedPart(content, signedData, "TODO: pgp-something"); |
533 | Q_ASSERT(!encodedBody.isEmpty()); | ||
534 | |||
535 | // if (!(format & Kleo::InlineOpenPGPFormat)) { // make a MIME message | ||
536 | // qDebug() << "making MIME message, format:" << format; | ||
537 | makeToplevelContentType(result, encrypt, hashAlgo); | ||
538 | |||
539 | // if (makeMultiMime(sign)) { // sign/enc PGPMime, sign SMIME | ||
540 | if (true) { // sign/enc PGPMime, sign SMIME | ||
541 | |||
542 | const QByteArray boundary = KMime::multiPartBoundary(); | ||
543 | result->contentType()->setBoundary(boundary); | ||
544 | |||
545 | result->assemble(); | ||
546 | //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head(); | ||
547 | |||
548 | // Build the encapsulated MIME parts. | ||
549 | // Build a MIME part holding the code information | ||
550 | // taking the body contents returned in ciphertext. | ||
551 | KMime::Content *code = new KMime::Content; | ||
552 | setNestedContentType(code, encrypt); | ||
553 | setNestedContentDisposition(code, encrypt); | ||
554 | |||
555 | if (encrypt) { // enc PGPMime (and / or sign) | ||
556 | |||
557 | //addPublicKeyAsAttachment(content, signingKeys[0]); | ||
558 | |||
559 | setBodyAndCTE(encodedBody, orig->contentType(), code); | ||
560 | |||
561 | // Build a MIME part holding the version information | ||
562 | // taking the body contents returned in | ||
563 | // structuring.data.bodyTextVersion. | ||
564 | KMime::Content *vers = new KMime::Content; | ||
565 | vers->contentType()->setMimeType("application/pgp-encrypted"); | ||
566 | vers->contentDisposition()->setDisposition(KMime::Headers::CDattachment); | ||
567 | vers->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); | ||
568 | vers->setBody("Version: 1"); | ||
569 | |||
570 | result->addContent(vers); | ||
571 | result->addContent(code); | ||
572 | } else { // sign PGPMime, sign SMIME | ||
573 | // if (format & Kleo::AnySMIME) { // sign SMIME | ||
574 | // code->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64); | ||
575 | // code->contentTransferEncoding()->needToEncode(); | ||
576 | // code->setBody(encodedBody); | ||
577 | // } else { // sign PGPMmime | ||
578 | setBodyAndCTE(encodedBody, orig->contentType(), code); | ||
579 | // } | ||
580 | result->addContent(orig); | ||
581 | result->addContent(code); | ||
582 | } | ||
583 | } else { //enc SMIME, sign/enc SMIMEOpaque | ||
584 | result->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64); | ||
585 | result->contentDisposition()->setDisposition(KMime::Headers::CDattachment); | ||
586 | result->contentDisposition()->setFilename(QStringLiteral("smime.p7m")); | ||
587 | 390 | ||
588 | result->assemble(); | 391 | auto publicKeyAppendResult = appendPublicKey(signedPart, attachedKey); |
589 | //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head(); | ||
590 | 392 | ||
591 | result->setBody(encodedBody); | 393 | // TODO: this is ugly (maybe use a std::unique_ptr for signedPart) |
592 | } | 394 | if (!publicKeyAppendResult) { |
593 | // } else { // sign/enc PGPInline | 395 | delete signedPart; |
594 | // result->setHead(orig->head()); | 396 | } |
595 | // result->parse(); | ||
596 | 397 | ||
597 | // // fixing ContentTransferEncoding | 398 | return publicKeyAppendResult; |
598 | // setBodyAndCTE(encodedBody, orig->contentType(), result); | ||
599 | // } | ||
600 | result->assemble(); | ||
601 | return result; | ||
602 | } | 399 | } |
603 | 400 | ||
604 | // bool binaryHint(Kleo::CryptoMessageFormat f) | 401 | KMime::Content *MailCrypto::processCrypto(KMime::Content *content, |
605 | // { | 402 | const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, |
606 | // switch (f) { | 403 | const GpgME::Key &attachedKey, MailCrypto::Protocol protocol) |
607 | // case Kleo::SMIMEFormat: | ||
608 | // case Kleo::SMIMEOpaqueFormat: | ||
609 | // return true; | ||
610 | // default: | ||
611 | // case Kleo::OpenPGPMIMEFormat: | ||
612 | // case Kleo::InlineOpenPGPFormat: | ||
613 | // return false; | ||
614 | // } | ||
615 | // } | ||
616 | // | ||
617 | // GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f) | ||
618 | // { | ||
619 | // switch (f) { | ||
620 | // case Kleo::SMIMEOpaqueFormat: | ||
621 | // return GpgME::NormalSignatureMode; | ||
622 | // case Kleo::InlineOpenPGPFormat: | ||
623 | // return GpgME::Clearsigned; | ||
624 | // default: | ||
625 | // case Kleo::SMIMEFormat: | ||
626 | // case Kleo::OpenPGPMIMEFormat: | ||
627 | // return GpgME::Detached; | ||
628 | // } | ||
629 | // } | ||
630 | |||
631 | KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol) | ||
632 | { | 404 | { |
633 | 405 | ||
634 | const bool sign = !signingKeys.empty(); | 406 | qDebug() << "Attaching key:" << attachedKey.shortKeyID() << "from processCrypto"; |
635 | 407 | ||
636 | if(!encryptionKeys.empty()) { | 408 | if(!encryptionKeys.empty()) { |
637 | return createEncryptedEmail(content, encryptionKeys, sign, signingKeys); | 409 | // TODO |
638 | } else if(sign) { | 410 | return createEncryptedEmail(content, encryptionKeys, attachedKey, signingKeys).value(); |
639 | return createSignedEmail(content, signingKeys); | 411 | } else if(!signingKeys.empty()) { |
412 | // TODO | ||
413 | return createSignedEmail(content, signingKeys, signingKeys[0]).value(); | ||
640 | } else { | 414 | } else { |
641 | qWarning() << "Processing cryptography, but neither signing nor encrypting"; | 415 | qWarning() << "Processing cryptography, but neither signing nor encrypting"; |
642 | return content; | 416 | return content; |
643 | } | 417 | } |
644 | |||
645 | const QGpgME::Protocol *const proto = protocol == MailCrypto::SMIME ? QGpgME::smime() : QGpgME::openpgp(); | ||
646 | Q_ASSERT(proto); | ||
647 | |||
648 | auto signingMode = GpgME::Detached; | ||
649 | bool armor = true; | ||
650 | bool textMode = false; | ||
651 | //const bool sign = !signingKeys.empty(); | ||
652 | const bool encrypt = !encryptionKeys.empty(); | ||
653 | |||
654 | QByteArray resultContent; | ||
655 | QByteArray hashAlgo; | ||
656 | //Trust provided keys and don't check them for validity | ||
657 | bool alwaysTrust = true; | ||
658 | if (sign && encrypt) { | ||
659 | std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(armor, textMode)); | ||
660 | const auto res = job->exec(signingKeys, encryptionKeys, canonicalizeContent(content), alwaysTrust, resultContent); | ||
661 | if (res.first.error().code()) { | ||
662 | qWarning() << "Signing failed:" << res.first.error().asString(); | ||
663 | return nullptr; | ||
664 | } else { | ||
665 | hashAlgo = res.first.createdSignature(0).hashAlgorithmAsString(); | ||
666 | } | ||
667 | if (res.second.error().code()) { | ||
668 | qWarning() << "Encryption failed:" << res.second.error().asString(); | ||
669 | return nullptr; | ||
670 | } | ||
671 | } else if (sign) { | ||
672 | std::unique_ptr<QGpgME::SignJob> job(proto->signJob(armor, textMode)); | ||
673 | auto result = job->exec(signingKeys, canonicalizeContent(content), signingMode, resultContent); | ||
674 | if (result.error().code()) { | ||
675 | qWarning() << "Signing failed:" << result.error().asString(); | ||
676 | return nullptr; | ||
677 | } | ||
678 | hashAlgo = result.createdSignature(0).hashAlgorithmAsString(); | ||
679 | } else if (encrypt) { | ||
680 | std::unique_ptr<QGpgME::EncryptJob> job(proto->encryptJob(armor, textMode)); | ||
681 | const auto result = job->exec(encryptionKeys, canonicalizeContent(content), alwaysTrust, resultContent); | ||
682 | if (result.error().code()) { | ||
683 | qWarning() << "Encryption failed:" << result.error().asString(); | ||
684 | return nullptr; | ||
685 | } | ||
686 | hashAlgo = "pgp-sha1"; | ||
687 | } else { | ||
688 | qWarning() << "Not signing or encrypting"; | ||
689 | return nullptr; | ||
690 | } | ||
691 | |||
692 | return composeHeadersAndBody(content, resultContent, encrypt, hashAlgo); | ||
693 | } | ||
694 | |||
695 | KMime::Content *MailCrypto::sign(KMime::Content *content, const std::vector<GpgME::Key> &signers) | ||
696 | { | ||
697 | return processCrypto(content, signers, {}, OPENPGP); | ||
698 | } | 418 | } |
699 | 419 | ||
700 | |||
701 | void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys) | 420 | void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys) |
702 | { | 421 | { |
703 | const QGpgME::Protocol *const backend = QGpgME::openpgp(); | 422 | const QGpgME::Protocol *const backend = QGpgME::openpgp(); |
@@ -753,4 +472,3 @@ std::vector<GpgME::Key> MailCrypto::findKeys(const QStringList &filter, bool fin | |||
753 | 472 | ||
754 | return keys; | 473 | return keys; |
755 | } | 474 | } |
756 | |||
diff --git a/framework/src/domain/mime/mailcrypto.h b/framework/src/domain/mime/mailcrypto.h index 0a6c2f4c..724d6427 100644 --- a/framework/src/domain/mime/mailcrypto.h +++ b/framework/src/domain/mime/mailcrypto.h | |||
@@ -30,8 +30,7 @@ namespace MailCrypto | |||
30 | OPENPGP, | 30 | OPENPGP, |
31 | SMIME | 31 | SMIME |
32 | }; | 32 | }; |
33 | KMime::Content *processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol); | 33 | KMime::Content *processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, const GpgME::Key &attachedKey, MailCrypto::Protocol protocol); |
34 | KMime::Content *sign(KMime::Content *content, const std::vector<GpgME::Key> &signers); | ||
35 | std::vector<GpgME::Key> findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false, Protocol protocol = OPENPGP); | 34 | std::vector<GpgME::Key> findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false, Protocol protocol = OPENPGP); |
36 | void importKeys(const std::vector<GpgME::Key> &keys); | 35 | void importKeys(const std::vector<GpgME::Key> &keys); |
37 | }; | 36 | }; |
diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp index 30f9a48d..399b6aa1 100644 --- a/framework/src/domain/mime/mailtemplates.cpp +++ b/framework/src/domain/mime/mailtemplates.cpp | |||
@@ -1025,7 +1025,11 @@ static KMime::Types::Mailbox::List stringListToMailboxes(const QStringList &list | |||
1025 | return mailboxes; | 1025 | return mailboxes; |
1026 | } | 1026 | } |
1027 | 1027 | ||
1028 | KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys) | 1028 | KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMessage, |
1029 | const QStringList &to, const QStringList &cc, const QStringList &bcc, | ||
1030 | const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, | ||
1031 | const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys, | ||
1032 | const std::vector<GpgME::Key> &encryptionKeys, const GpgME::Key &attachedKey) | ||
1029 | { | 1033 | { |
1030 | auto mail = existingMessage; | 1034 | auto mail = existingMessage; |
1031 | if (!mail) { | 1035 | if (!mail) { |
@@ -1089,7 +1093,8 @@ KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMes | |||
1089 | 1093 | ||
1090 | QByteArray bodyData; | 1094 | QByteArray bodyData; |
1091 | if (!signingKeys.empty() || !encryptionKeys.empty()) { | 1095 | if (!signingKeys.empty() || !encryptionKeys.empty()) { |
1092 | auto result = MailCrypto::processCrypto(bodyPart.get(), signingKeys, encryptionKeys, MailCrypto::OPENPGP); | 1096 | auto result = MailCrypto::processCrypto( |
1097 | bodyPart.get(), signingKeys, encryptionKeys, attachedKey, MailCrypto::OPENPGP); | ||
1093 | if (!result) { | 1098 | if (!result) { |
1094 | qWarning() << "Signing failed"; | 1099 | qWarning() << "Signing failed"; |
1095 | return {}; | 1100 | return {}; |
diff --git a/framework/src/domain/mime/mailtemplates.h b/framework/src/domain/mime/mailtemplates.h index 9447e169..154b76a2 100644 --- a/framework/src/domain/mime/mailtemplates.h +++ b/framework/src/domain/mime/mailtemplates.h | |||
@@ -38,5 +38,5 @@ namespace MailTemplates | |||
38 | void forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback); | 38 | void forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback); |
39 | QString plaintextContent(const KMime::Message::Ptr &origMsg); | 39 | QString plaintextContent(const KMime::Message::Ptr &origMsg); |
40 | QString body(const KMime::Message::Ptr &msg, bool &isHtml); | 40 | QString body(const KMime::Message::Ptr &msg, bool &isHtml); |
41 | KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys = {}, const std::vector<GpgME::Key> &encryptionKeys = {}); | 41 | KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys = {}, const std::vector<GpgME::Key> &encryptionKeys = {}, const GpgME::Key &attachedKey = {}); |
42 | }; | 42 | }; |
diff --git a/framework/src/errors.h b/framework/src/errors.h index f6c6cfab..9c48f32a 100644 --- a/framework/src/errors.h +++ b/framework/src/errors.h | |||
@@ -20,9 +20,13 @@ public: | |||
20 | 20 | ||
21 | // For implicit conversions when doing makeUnexpected(other) | 21 | // For implicit conversions when doing makeUnexpected(other) |
22 | template <typename Other> | 22 | template <typename Other> |
23 | constexpr explicit Unexpected(const Unexpected<Other> &error) : mValue(error.value()) {} | 23 | constexpr explicit Unexpected(const Unexpected<Other> &error) : mValue(error.value()) |
24 | { | ||
25 | } | ||
24 | template <typename Other> | 26 | template <typename Other> |
25 | constexpr explicit Unexpected(Unexpected<Other> &&error) : mValue(std::move(error.value())) {} | 27 | constexpr explicit Unexpected(Unexpected<Other> &&error) : mValue(std::move(error.value())) |
28 | { | ||
29 | } | ||
26 | 30 | ||
27 | constexpr const Error &value() const & | 31 | constexpr const Error &value() const & |
28 | { | 32 | { |
@@ -82,7 +86,7 @@ protected: | |||
82 | 86 | ||
83 | constexpr void copyPartFromOther(const StorageBase &other) | 87 | constexpr void copyPartFromOther(const StorageBase &other) |
84 | { | 88 | { |
85 | if (isValue) { | 89 | if (mIsValue) { |
86 | mValue = other.mValue; | 90 | mValue = other.mValue; |
87 | } else { | 91 | } else { |
88 | mError = other.mError; | 92 | mError = other.mError; |
@@ -91,40 +95,40 @@ protected: | |||
91 | 95 | ||
92 | void movePartFromOther(StorageBase &&other) | 96 | void movePartFromOther(StorageBase &&other) |
93 | { | 97 | { |
94 | if (isValue) { | 98 | if (mIsValue) { |
95 | mValue = std::move(other.mValue); | 99 | mValue = std::move(other.mValue); |
96 | } else { | 100 | } else { |
97 | mError = std::move(other.mError); | 101 | mError = std::move(other.mError); |
98 | } | 102 | } |
99 | } | 103 | } |
100 | 104 | ||
101 | StorageBase(const StorageBase &other) : isValue(other.isValue) | 105 | StorageBase(const StorageBase &other) : mIsValue(other.mIsValue) |
102 | { | 106 | { |
103 | copyPartFromOther(other); | 107 | copyPartFromOther(other); |
104 | } | 108 | } |
105 | 109 | ||
106 | StorageBase(StorageBase &&other) : isValue(other.isValue) | 110 | StorageBase(StorageBase &&other) : mIsValue(other.mIsValue) |
107 | { | 111 | { |
108 | movePartFromOther(std::move(other)); | 112 | movePartFromOther(std::move(other)); |
109 | } | 113 | } |
110 | 114 | ||
111 | constexpr StorageBase &operator=(const StorageBase &other) | 115 | constexpr StorageBase &operator=(const StorageBase &other) |
112 | { | 116 | { |
113 | isValue = other.isValue; | 117 | mIsValue = other.mIsValue; |
114 | copyPartFromOther(other); | 118 | copyPartFromOther(other); |
115 | return *this; | 119 | return *this; |
116 | } | 120 | } |
117 | 121 | ||
118 | constexpr StorageBase &operator=(StorageBase &&other) | 122 | constexpr StorageBase &operator=(StorageBase &&other) |
119 | { | 123 | { |
120 | isValue = other.isValue; | 124 | mIsValue = other.mIsValue; |
121 | movePartFromOther(other); | 125 | movePartFromOther(other); |
122 | return *this; | 126 | return *this; |
123 | } | 127 | } |
124 | 128 | ||
125 | ~StorageBase() | 129 | ~StorageBase() |
126 | { | 130 | { |
127 | if (isValue) { | 131 | if (mIsValue) { |
128 | mValue.~Type(); | 132 | mValue.~Type(); |
129 | } else { | 133 | } else { |
130 | mError.~Unexpected<Error>(); | 134 | mError.~Unexpected<Error>(); |
@@ -135,13 +139,13 @@ protected: | |||
135 | 139 | ||
136 | template <typename... Args> | 140 | template <typename... Args> |
137 | constexpr StorageBase(tags::Expected, Args &&... args) | 141 | constexpr StorageBase(tags::Expected, Args &&... args) |
138 | : mValue(std::forward<Args>(args)...), isValue(true) | 142 | : mValue(std::forward<Args>(args)...), mIsValue(true) |
139 | { | 143 | { |
140 | } | 144 | } |
141 | 145 | ||
142 | template <typename... Args> | 146 | template <typename... Args> |
143 | constexpr StorageBase(tags::Unexpected, Args &&... args) | 147 | constexpr StorageBase(tags::Unexpected, Args &&... args) |
144 | : mError(std::forward<Args>(args)...), isValue(false) | 148 | : mError(std::forward<Args>(args)...), mIsValue(false) |
145 | { | 149 | { |
146 | } | 150 | } |
147 | 151 | ||
@@ -150,7 +154,7 @@ protected: | |||
150 | Unexpected<Error> mError; | 154 | Unexpected<Error> mError; |
151 | Type mValue; | 155 | Type mValue; |
152 | }; | 156 | }; |
153 | bool isValue; | 157 | bool mIsValue; |
154 | }; | 158 | }; |
155 | 159 | ||
156 | // Write functions here when storage related and when Type == void | 160 | // Write functions here when storage related and when Type == void |
@@ -158,16 +162,16 @@ template <typename Error> | |||
158 | struct StorageBase<Error, void> | 162 | struct StorageBase<Error, void> |
159 | { | 163 | { |
160 | protected: | 164 | protected: |
161 | constexpr StorageBase(tags::Expected) : isValue(true) {} | 165 | constexpr StorageBase(tags::Expected) : mIsValue(true) {} |
162 | 166 | ||
163 | template <typename... Args> | 167 | template <typename... Args> |
164 | constexpr StorageBase(tags::Unexpected, Args &&... args) | 168 | constexpr StorageBase(tags::Unexpected, Args &&... args) |
165 | : mError(std::forward<Args>(args)...), isValue(false) | 169 | : mError(std::forward<Args>(args)...), mIsValue(false) |
166 | { | 170 | { |
167 | } | 171 | } |
168 | 172 | ||
169 | Unexpected<Error> mError; | 173 | Unexpected<Error> mError; |
170 | bool isValue; | 174 | bool mIsValue; |
171 | }; | 175 | }; |
172 | 176 | ||
173 | // Write functions here when storage related, whether Type is void or not | 177 | // Write functions here when storage related, whether Type is void or not |
@@ -205,8 +209,11 @@ struct ExpectedBase : detail::Storage<Error, Type> | |||
205 | { | 209 | { |
206 | } | 210 | } |
207 | 211 | ||
212 | // Warning: will crash if this is an error. You should always check this is | ||
213 | // an expected value before calling `.value()` | ||
208 | constexpr const Type &value() const & | 214 | constexpr const Type &value() const & |
209 | { | 215 | { |
216 | Q_ASSERT(this->mIsValue); | ||
210 | return this->mValue; | 217 | return this->mValue; |
211 | } | 218 | } |
212 | }; | 219 | }; |
@@ -243,4 +250,13 @@ public: | |||
243 | { | 250 | { |
244 | return this->mError.value(); | 251 | return this->mError.value(); |
245 | } | 252 | } |
253 | |||
254 | constexpr bool isValue() const | ||
255 | { | ||
256 | return this->mIsValue; | ||
257 | } | ||
258 | constexpr explicit operator bool() const | ||
259 | { | ||
260 | return this->mIsValue; | ||
261 | } | ||
246 | }; | 262 | }; |