1 /*
2 SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
3 SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "composerviewbase.h"
9
10 #include "attachment/attachmentcontrollerbase.h"
11 #include "attachment/attachmentmodel.h"
12 #include "composer-ng/richtextcomposerng.h"
13 #include "composer-ng/richtextcomposersignatures.h"
14 #include "composer.h"
15 #include "composer/keyresolver.h"
16 #include "composer/signaturecontroller.h"
17 #include "draftstatus/draftstatus.h"
18 #include "imagescaling/imagescalingutils.h"
19 #include "job/emailaddressresolvejob.h"
20 #include "part/globalpart.h"
21 #include "part/infopart.h"
22 #include "utils/kleo_util.h"
23 #include "utils/util.h"
24 #include "utils/util_p.h"
25 #include <KPIMTextEdit/RichTextComposerControler>
26 #include <KPIMTextEdit/RichTextComposerImages>
27
28 #include "sendlater/sendlatercreatejob.h"
29 #include "sendlater/sendlaterinfo.h"
30
31 #include "helper/messagehelper.h"
32 #include <PimCommonAkonadi/RecentAddresses>
33
34 #include "settings/messagecomposersettings.h"
35 #include <MessageComposer/RecipientsEditor>
36
37 #include <KCursorSaver>
38 #include <KIdentityManagement/Identity>
39 #include <MimeTreeParser/ObjectTreeParser>
40 #include <MimeTreeParser/SimpleObjectTreeSource>
41 #include <Sonnet/DictionaryComboBox>
42
43 #include <MessageCore/AutocryptStorage>
44 #include <MessageCore/NodeHelper>
45 #include <MessageCore/StringUtil>
46
47 #include <MailTransport/TransportComboBox>
48 #include <MailTransport/TransportManager>
49 #include <MailTransportAkonadi/MessageQueueJob>
50
51 #include <Akonadi/CollectionComboBox>
52 #include <Akonadi/CollectionFetchJob>
53 #include <Akonadi/ItemCreateJob>
54 #include <Akonadi/KMime/MessageFlags>
55 #include <Akonadi/KMime/SpecialMailCollections>
56
57 #include <KEmailAddress>
58 #include <KIdentityManagement/IdentityCombo>
59 #include <KIdentityManagement/IdentityManager>
60
61 #include "messagecomposer_debug.h"
62
63 #include <QGpgME/ExportJob>
64 #include <QGpgME/ImportJob>
65 #include <QGpgME/Protocol>
66 #include <gpgme++/context.h>
67 #include <gpgme++/gpgmepp_version.h>
68 #include <gpgme++/importresult.h>
69
70 #include <KLocalizedString>
71 #include <KMessageBox>
72 #include <QSaveFile>
73
74 #include <QDir>
75 #include <QStandardPaths>
76 #include <QTemporaryDir>
77 #include <QTimer>
78 #include <QUuid>
79 #include <followupreminder/followupremindercreatejob.h>
80
81 using namespace MessageComposer;
82
ComposerViewBase(QObject * parent,QWidget * parentGui)83 ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
84 : QObject(parent)
85 , m_msg(KMime::Message::Ptr(new KMime::Message))
86 , m_parentWidget(parentGui)
87 , m_cryptoMessageFormat(Kleo::AutoFormat)
88 , m_autoSaveInterval(60000) // default of 1 min
89 {
90 m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set.
91
92 initAutoSave();
93 }
94
95 ComposerViewBase::~ComposerViewBase() = default;
96
isComposing() const97 bool ComposerViewBase::isComposing() const
98 {
99 return !m_composers.isEmpty();
100 }
101
setMessage(const KMime::Message::Ptr & msg,bool allowDecryption)102 void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg, bool allowDecryption)
103 {
104 if (m_attachmentModel) {
105 const auto attachments{m_attachmentModel->attachments()};
106 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
107 if (!m_attachmentModel->removeAttachment(attachment)) {
108 qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
109 }
110 }
111 }
112 m_msg = msg;
113 if (m_recipientsEditor) {
114 m_recipientsEditor->clear();
115 bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), MessageComposer::Recipient::To);
116 if (!resultTooManyRecipients) {
117 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc);
118 }
119 if (!resultTooManyRecipients) {
120 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc);
121 }
122 if (!resultTooManyRecipients) {
123 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), MessageComposer::Recipient::ReplyTo);
124 }
125 m_recipientsEditor->setFocusBottom();
126
127 if (!resultTooManyRecipients) {
128 // If we are loading from a draft, load unexpanded aliases as well
129 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-To")) {
130 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
131 for (const QString &addr : spl) {
132 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::To)) {
133 resultTooManyRecipients = true;
134 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
135 break;
136 }
137 }
138 }
139 }
140 if (!resultTooManyRecipients) {
141 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
142 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
143 for (const QString &addr : spl) {
144 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Cc)) {
145 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
146 resultTooManyRecipients = true;
147 break;
148 }
149 }
150 }
151 }
152 if (!resultTooManyRecipients) {
153 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
154 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
155 for (const QString &addr : spl) {
156 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
157 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
158 resultTooManyRecipients = true;
159 break;
160 }
161 }
162 }
163 }
164 if (!resultTooManyRecipients) {
165 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
166 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
167 for (const QString &addr : spl) {
168 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::ReplyTo)) {
169 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
170 resultTooManyRecipients = true;
171 break;
172 }
173 }
174 }
175 }
176 Q_EMIT tooManyRecipient(resultTooManyRecipients);
177 }
178 // First, we copy the message and then parse it to the object tree parser.
179 // The otp gets the message text out of it, in textualContent(), and also decrypts
180 // the message if necessary.
181 auto msgContent = new KMime::Content;
182 msgContent->setContent(m_msg->encodedContent());
183 msgContent->parse();
184 MimeTreeParser::SimpleObjectTreeSource emptySource;
185 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
186 emptySource.setDecryptMessage(allowDecryption);
187 otp.parseObjectTree(msgContent);
188
189 // Load the attachments
190 const auto attachmentsOfExtraContents{otp.nodeHelper()->attachmentsOfExtraContents()};
191 for (const auto &att : attachmentsOfExtraContents) {
192 addAttachmentPart(att);
193 }
194 const auto attachments{msgContent->attachments()};
195 for (const auto &att : attachments) {
196 addAttachmentPart(att);
197 }
198
199 int transportId = -1;
200 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
201 transportId = hdr->asUnicodeString().toInt();
202 }
203
204 if (m_transport) {
205 const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId);
206 if (transport) {
207 if (!m_transport->setCurrentTransport(transport->id())) {
208 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to find transport id" << transport->id();
209 }
210 }
211 }
212
213 // Set the HTML text and collect HTML images
214 QString htmlContent = otp.htmlContent();
215 if (htmlContent.isEmpty()) {
216 m_editor->setPlainText(otp.plainTextContent());
217 } else {
218 // Bug 372085 <div id="name"> is replaced in qtextedit by <a id="name">... => break url
219 htmlContent.replace(QRegularExpression(QStringLiteral("<div\\s*id=\".*\">")), QStringLiteral("<div>"));
220 m_editor->setHtml(htmlContent);
221 Q_EMIT enableHtml();
222 collectImages(m_msg.data());
223 }
224
225 if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
226 m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
227 }
228 delete msgContent;
229 }
230
updateTemplate(const KMime::Message::Ptr & msg)231 void ComposerViewBase::updateTemplate(const KMime::Message::Ptr &msg)
232 {
233 // First, we copy the message and then parse it to the object tree parser.
234 // The otp gets the message text out of it, in textualContent(), and also decrypts
235 // the message if necessary.
236 auto msgContent = new KMime::Content;
237 msgContent->setContent(msg->encodedContent());
238 msgContent->parse();
239 MimeTreeParser::SimpleObjectTreeSource emptySource;
240 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
241 otp.parseObjectTree(msgContent);
242 // Set the HTML text and collect HTML images
243 if (!otp.htmlContent().isEmpty()) {
244 m_editor->setHtml(otp.htmlContent());
245 Q_EMIT enableHtml();
246 collectImages(msg.data());
247 } else {
248 m_editor->setPlainText(otp.plainTextContent());
249 }
250
251 if (auto hdr = msg->headerByType("X-KMail-CursorPos")) {
252 m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toInt());
253 }
254 delete msgContent;
255 }
256
saveMailSettings()257 void ComposerViewBase::saveMailSettings()
258 {
259 const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
260 auto header = new KMime::Headers::Generic("X-KMail-Transport");
261 header->fromUnicodeString(QString::number(m_transport->currentTransportId()), "utf-8");
262 m_msg->setHeader(header);
263
264 header = new KMime::Headers::Generic("X-KMail-Transport-Name");
265 header->fromUnicodeString(m_transport->currentText(), "utf-8");
266 m_msg->setHeader(header);
267
268 header = new KMime::Headers::Generic("X-KMail-Fcc");
269 header->fromUnicodeString(QString::number(m_fccCollection.id()), "utf-8");
270 m_msg->setHeader(header);
271
272 header = new KMime::Headers::Generic("X-KMail-Identity");
273 header->fromUnicodeString(QString::number(identity.uoid()), "utf-8");
274 m_msg->setHeader(header);
275
276 header = new KMime::Headers::Generic("X-KMail-Identity-Name");
277 header->fromUnicodeString(identity.identityName(), "utf-8");
278 m_msg->setHeader(header);
279
280 header = new KMime::Headers::Generic("X-KMail-Dictionary");
281 header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
282 m_msg->setHeader(header);
283
284 // Save the quote prefix which is used for this message. Each message can have
285 // a different quote prefix, for example depending on the original sender.
286 if (m_editor->quotePrefixName().isEmpty()) {
287 m_msg->removeHeader("X-KMail-QuotePrefix");
288 } else {
289 header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
290 header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
291 m_msg->setHeader(header);
292 }
293
294 if (m_editor->composerControler()->isFormattingUsed()) {
295 qCDebug(MESSAGECOMPOSER_LOG) << "HTML mode";
296 header = new KMime::Headers::Generic("X-KMail-Markup");
297 header->fromUnicodeString(QStringLiteral("true"), "utf-8");
298 m_msg->setHeader(header);
299 } else {
300 m_msg->removeHeader("X-KMail-Markup");
301 qCDebug(MESSAGECOMPOSER_LOG) << "Plain text";
302 }
303 }
304
clearFollowUp()305 void ComposerViewBase::clearFollowUp()
306 {
307 mFollowUpDate = QDate();
308 mFollowUpCollection = Akonadi::Collection();
309 }
310
send(MessageComposer::MessageSender::SendMethod method,MessageComposer::MessageSender::SaveIn saveIn,bool checkMailDispatcher)311 void ComposerViewBase::send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher)
312 {
313 mSendMethod = method;
314 mSaveIn = saveIn;
315
316 KCursorSaver saver(Qt::WaitCursor);
317 const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
318
319 if (identity.attachVcard() && m_attachmentController->attachOwnVcard()) {
320 const QString vcardFileName = identity.vCardFile();
321 if (!vcardFileName.isEmpty()) {
322 m_attachmentController->addAttachmentUrlSync(QUrl::fromLocalFile(vcardFileName));
323 }
324 }
325 saveMailSettings();
326
327 if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
328 const QString keepBtnText =
329 m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
330 const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
331 int ret = KMessageBox::warningYesNoCancel(m_parentWidget,
332 i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
333 "<p>do you want to delete your markup?</p></qt>"),
334 i18n("Sign/Encrypt Message?"),
335 KGuiItem(yesBtnText),
336 KGuiItem(keepBtnText));
337 if (KMessageBox::Cancel == ret) {
338 return;
339 }
340 if (KMessageBox::No == ret) {
341 m_encrypt = false;
342 m_sign = false;
343 } else {
344 Q_EMIT disableHtml(NoConfirmationNeeded);
345 }
346 }
347
348 if (m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone) {
349 // we can't use the state of the mail itself, to remember the
350 // signing and encryption state, so let's add a header instead
351 DraftSignatureState(m_msg).setState(m_sign);
352 DraftEncryptionState(m_msg).setState(m_encrypt);
353 DraftCryptoMessageFormatState(m_msg).setState(m_cryptoMessageFormat);
354 } else {
355 removeDraftCryptoHeaders(m_msg);
356 }
357
358 if (mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) {
359 if (!MessageComposer::Util::sendMailDispatcherIsOnline(m_parentWidget)) {
360 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to set sendmaildispatcher online. Please verify it";
361 return;
362 }
363 }
364
365 readyForSending();
366 }
367
setCustomHeader(const QMap<QByteArray,QString> & customHeader)368 void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
369 {
370 m_customHeader = customHeader;
371 }
372
readyForSending()373 void ComposerViewBase::readyForSending()
374 {
375 qCDebug(MESSAGECOMPOSER_LOG) << "Entering readyForSending";
376 if (!m_msg) {
377 qCDebug(MESSAGECOMPOSER_LOG) << "m_msg == 0!";
378 return;
379 }
380
381 if (!m_composers.isEmpty()) {
382 // This may happen if e.g. the autosave timer calls applyChanges.
383 qCDebug(MESSAGECOMPOSER_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
384 return;
385 }
386
387 // first, expand all addresses
388 auto job = new MessageComposer::EmailAddressResolveJob(this);
389 const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
390 if (!identity.isNull()) {
391 job->setDefaultDomainName(identity.defaultDomainName());
392 }
393 job->setFrom(from());
394 job->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
395 job->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
396 job->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
397 job->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
398
399 connect(job, &MessageComposer::EmailAddressResolveJob::result, this, &ComposerViewBase::slotEmailAddressResolved);
400 job->start();
401 }
402
slotEmailAddressResolved(KJob * job)403 void ComposerViewBase::slotEmailAddressResolved(KJob *job)
404 {
405 if (job->error()) {
406 qCWarning(MESSAGECOMPOSER_LOG) << "An error occurred while resolving the email addresses:" << job->errorString();
407 // This error could be caused by a broken search infrastructure, so we ignore it for now
408 // to not block sending emails completely.
409 }
410
411 bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled();
412
413 const MessageComposer::EmailAddressResolveJob *resolveJob = qobject_cast<MessageComposer::EmailAddressResolveJob *>(job);
414 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
415 mExpandedFrom = resolveJob->expandedFrom();
416 mExpandedTo = resolveJob->expandedTo();
417 mExpandedCc = resolveJob->expandedCc();
418 mExpandedBcc = resolveJob->expandedBcc();
419 mExpandedReplyTo = resolveJob->expandedReplyTo();
420 if (autoresizeImage) {
421 QStringList listEmails;
422 listEmails << mExpandedFrom;
423 listEmails << mExpandedTo;
424 listEmails << mExpandedCc;
425 listEmails << mExpandedBcc;
426 listEmails << mExpandedReplyTo;
427 MessageComposer::Utils resizeUtils;
428 autoresizeImage = resizeUtils.filterRecipients(listEmails);
429 }
430 } else { // saved to draft, so keep the old values, not very nice.
431 mExpandedFrom = from();
432 const auto recipients{m_recipientsEditor->recipients()};
433 for (const MessageComposer::Recipient::Ptr &r : recipients) {
434 switch (r->type()) {
435 case MessageComposer::Recipient::To:
436 mExpandedTo << r->email();
437 break;
438 case MessageComposer::Recipient::Cc:
439 mExpandedCc << r->email();
440 break;
441 case MessageComposer::Recipient::Bcc:
442 mExpandedBcc << r->email();
443 break;
444 case MessageComposer::Recipient::ReplyTo:
445 mExpandedReplyTo << r->email();
446 break;
447 case MessageComposer::Recipient::Undefined:
448 Q_ASSERT(!"Unknown recipient type!");
449 break;
450 }
451 }
452 QStringList unExpandedTo;
453 QStringList unExpandedCc;
454 QStringList unExpandedBcc;
455 QStringList unExpandedReplyTo;
456 const auto expandedToLst{resolveJob->expandedTo()};
457 for (const QString &exp : expandedToLst) {
458 if (!mExpandedTo.contains(exp)) { // this address was expanded, so save it explicitly
459 unExpandedTo << exp;
460 }
461 }
462 const auto expandedCcLst{resolveJob->expandedCc()};
463 for (const QString &exp : expandedCcLst) {
464 if (!mExpandedCc.contains(exp)) {
465 unExpandedCc << exp;
466 }
467 }
468 const auto expandedBCcLst{resolveJob->expandedBcc()};
469 for (const QString &exp : expandedBCcLst) {
470 if (!mExpandedBcc.contains(exp)) { // this address was expanded, so save it explicitly
471 unExpandedBcc << exp;
472 }
473 }
474 const auto expandedReplyLst{resolveJob->expandedReplyTo()};
475 for (const QString &exp : expandedReplyLst) {
476 if (!mExpandedReplyTo.contains(exp)) { // this address was expanded, so save it explicitly
477 unExpandedReplyTo << exp;
478 }
479 }
480 auto header = new KMime::Headers::Generic("X-KMail-UnExpanded-To");
481 header->from7BitString(unExpandedTo.join(QLatin1String(", ")).toLatin1());
482 m_msg->setHeader(header);
483 header = new KMime::Headers::Generic("X-KMail-UnExpanded-CC");
484 header->from7BitString(unExpandedCc.join(QLatin1String(", ")).toLatin1());
485 m_msg->setHeader(header);
486 header = new KMime::Headers::Generic("X-KMail-UnExpanded-BCC");
487 header->from7BitString(unExpandedBcc.join(QLatin1String(", ")).toLatin1());
488 m_msg->setHeader(header);
489 header = new KMime::Headers::Generic("X-KMail-UnExpanded-Reply-To");
490 header->from7BitString(unExpandedReplyTo.join(QLatin1String(", ")).toLatin1());
491 m_msg->setHeader(header);
492 autoresizeImage = false;
493 }
494
495 Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
496 // checks for emptiness before calling it
497 // so just ensure it actually is empty
498 // and document it
499 // we first figure out if we need to create multiple messages with different crypto formats
500 // if so, we create a composer per format
501 // if we aren't signing or encrypting, this just returns a single empty message
502 if (m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone && !mSendLaterInfo) {
503 auto composer = new MessageComposer::Composer;
504 composer->setNoCrypto(true);
505 m_composers.append(composer);
506 } else {
507 bool wasCanceled = false;
508 m_composers = generateCryptoMessages(wasCanceled);
509 if (wasCanceled) {
510 return;
511 }
512 }
513
514 if (m_composers.isEmpty()) {
515 Q_EMIT failed(i18n("It was not possible to create a message composer."));
516 return;
517 }
518
519 if (autoresizeImage) {
520 if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) {
521 if (m_attachmentModel) {
522 MessageComposer::Utils resizeUtils;
523 if (resizeUtils.containsImage(m_attachmentModel->attachments())) {
524 const int rc = KMessageBox::warningYesNo(m_parentWidget,
525 i18n("Do you want to resize images?"),
526 i18n("Auto Resize Images"),
527 KGuiItem(i18nc("@action:button", "Auto Resize")),
528 KGuiItem(i18nc("@action:button", "Do Not Resize")));
529 if (rc == KMessageBox::Yes) {
530 autoresizeImage = true;
531 } else {
532 autoresizeImage = false;
533 }
534 } else {
535 autoresizeImage = false;
536 }
537 }
538 }
539 }
540 // Compose each message and prepare it for queueing, sending, or storing
541
542 // working copy in case composers instantly emit result
543 const auto composers = m_composers;
544 for (MessageComposer::Composer *composer : composers) {
545 fillComposer(composer, UseExpandedRecipients, autoresizeImage);
546 connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
547 composer->start();
548 qCDebug(MESSAGECOMPOSER_LOG) << "Started a composer for sending!";
549 }
550 }
551
552 namespace
553 {
554 // helper methods for reading encryption settings
555
encryptKeyNearExpiryWarningThresholdInDays()556 inline int encryptKeyNearExpiryWarningThresholdInDays()
557 {
558 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
559 return -1;
560 }
561 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
562 return qMax(1, num);
563 }
564
signingKeyNearExpiryWarningThresholdInDays()565 inline int signingKeyNearExpiryWarningThresholdInDays()
566 {
567 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
568 return -1;
569 }
570 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnSignKeyNearExpiryThresholdDays();
571 return qMax(1, num);
572 }
573
encryptRootCertNearExpiryWarningThresholdInDays()574 inline int encryptRootCertNearExpiryWarningThresholdInDays()
575 {
576 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
577 return -1;
578 }
579 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
580 return qMax(1, num);
581 }
582
signingRootCertNearExpiryWarningThresholdInDays()583 inline int signingRootCertNearExpiryWarningThresholdInDays()
584 {
585 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
586 return -1;
587 }
588 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnSignRootNearExpiryThresholdDays();
589 return qMax(1, num);
590 }
591
encryptChainCertNearExpiryWarningThresholdInDays()592 inline int encryptChainCertNearExpiryWarningThresholdInDays()
593 {
594 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
595 return -1;
596 }
597 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
598 return qMax(1, num);
599 }
600
signingChainCertNearExpiryWarningThresholdInDays()601 inline int signingChainCertNearExpiryWarningThresholdInDays()
602 {
603 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
604 return -1;
605 }
606 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnSignChaincertNearExpiryThresholdDays();
607 return qMax(1, num);
608 }
609
encryptToSelf()610 inline bool encryptToSelf()
611 {
612 return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf();
613 }
614
showKeyApprovalDialog()615 inline bool showKeyApprovalDialog()
616 {
617 return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
618 }
619 } // nameless namespace
620
addKeysToContext(const QString & gnupgHome,const QVector<QPair<QStringList,std::vector<GpgME::Key>>> & data,const std::map<QByteArray,QString> & autocryptMap)621 bool ComposerViewBase::addKeysToContext(const QString &gnupgHome,
622 const QVector<QPair<QStringList, std::vector<GpgME::Key>>> &data,
623 const std::map<QByteArray, QString> &autocryptMap)
624 {
625 bool needSpecialContext = false;
626
627 for (const auto &p : data) {
628 for (const auto &k : p.second) {
629 const auto it = autocryptMap.find(k.primaryFingerprint());
630 if (it != autocryptMap.end()) {
631 needSpecialContext = true;
632 break;
633 }
634 }
635 if (needSpecialContext) {
636 break;
637 }
638 }
639
640 // qDebug() << "addKeysToContext: " << needSpecialContext;
641
642 if (!needSpecialContext) {
643 return false;
644 }
645 const QGpgME::Protocol *proto(QGpgME::openpgp());
646
647 const auto storage = MessageCore::AutocryptStorage::self();
648 QEventLoop loop;
649 int runningJobs = 0;
650 for (const auto &p : data) {
651 for (const auto &k : p.second) {
652 const auto it = autocryptMap.find(k.primaryFingerprint());
653 if (it == autocryptMap.end()) {
654 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "via Export/Import";
655 auto exportJob = proto->publicKeyExportJob(false);
656 connect(exportJob,
657 &QGpgME::ExportJob::result,
658 [&gnupgHome, &proto, &runningJobs, &loop, &k](const GpgME::Error &result,
659 const QByteArray &keyData,
660 const QString &auditLogAsHtml,
661 const GpgME::Error &auditLogError) {
662 Q_UNUSED(auditLogAsHtml);
663 Q_UNUSED(auditLogError);
664 if (result) {
665 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to export " << k.primaryFingerprint() << result.asString();
666 --runningJobs;
667 if (runningJobs < 1) {
668 loop.quit();
669 }
670 }
671
672 auto importJob = proto->importJob();
673 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
674 importJob->exec(keyData);
675 importJob->deleteLater();
676 --runningJobs;
677 if (runningJobs < 1) {
678 loop.quit();
679 }
680 });
681 QStringList patterns;
682 patterns << QString::fromUtf8(k.primaryFingerprint());
683 runningJobs++;
684 exportJob->start(patterns);
685 #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
686 exportJob->setExportFlags(GpgME::Context::ExportMinimal);
687 #endif
688 } else {
689 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage";
690 const auto recipient = storage->getRecipient(it->second.toUtf8());
691 auto key = recipient->gpgKey();
692 auto keydata = recipient->gpgKeydata();
693 if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) {
694 qCDebug(MESSAGECOMPOSER_LOG) << "Using gossipkey";
695 keydata = recipient->gossipKeydata();
696 }
697 auto importJob = proto->importJob();
698 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
699 const auto result = importJob->exec(keydata);
700 importJob->deleteLater();
701 }
702 }
703 }
704 loop.exec();
705 return true;
706 }
707
generateCryptoMessages(bool & wasCanceled)708 QVector<MessageComposer::Composer *> ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
709 {
710 const KIdentityManagement::Identity &id = m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
711
712 qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
713 QScopedPointer<Kleo::KeyResolver> keyResolver(new Kleo::KeyResolver(encryptToSelf(),
714 showKeyApprovalDialog(),
715 id.pgpAutoEncrypt(),
716 m_cryptoMessageFormat,
717 encryptKeyNearExpiryWarningThresholdInDays(),
718 signingKeyNearExpiryWarningThresholdInDays(),
719 encryptRootCertNearExpiryWarningThresholdInDays(),
720 signingRootCertNearExpiryWarningThresholdInDays(),
721 encryptChainCertNearExpiryWarningThresholdInDays(),
722 signingChainCertNearExpiryWarningThresholdInDays()));
723
724 keyResolver->setAutocryptEnabled(id.autocryptEnabled());
725
726 QStringList encryptToSelfKeys;
727 QStringList signKeys;
728
729 bool signSomething = m_sign;
730 bool doSignCompletely = m_sign;
731 bool encryptSomething = m_encrypt;
732 bool doEncryptCompletely = m_encrypt;
733
734 // Add encryptionkeys from id to keyResolver
735 qCDebug(MESSAGECOMPOSER_LOG) << id.pgpEncryptionKey().isEmpty() << id.smimeEncryptionKey().isEmpty();
736 if (!id.pgpEncryptionKey().isEmpty()) {
737 encryptToSelfKeys.push_back(QLatin1String(id.pgpEncryptionKey()));
738 }
739 if (!id.smimeEncryptionKey().isEmpty()) {
740 encryptToSelfKeys.push_back(QLatin1String(id.smimeEncryptionKey()));
741 }
742 if (keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
743 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
744 return QVector<MessageComposer::Composer *>();
745 }
746
747 // Add signingkeys from id to keyResolver
748 if (!id.pgpSigningKey().isEmpty()) {
749 signKeys.push_back(QLatin1String(id.pgpSigningKey()));
750 }
751 if (!id.smimeSigningKey().isEmpty()) {
752 signKeys.push_back(QLatin1String(id.smimeSigningKey()));
753 }
754 if (keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
755 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
756 return QVector<MessageComposer::Composer *>();
757 }
758
759 if (m_attachmentModel) {
760 const auto attachments = m_attachmentModel->attachments();
761 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
762 if (attachment->isSigned()) {
763 signSomething = true;
764 } else {
765 doEncryptCompletely = false;
766 }
767 if (attachment->isEncrypted()) {
768 encryptSomething = true;
769 } else {
770 doSignCompletely = false;
771 }
772 }
773 }
774
775 const QStringList recipients = mExpandedTo + mExpandedCc;
776 const QStringList bcc(mExpandedBcc);
777
778 keyResolver->setPrimaryRecipients(recipients);
779 keyResolver->setSecondaryRecipients(bcc);
780
781 bool result = true;
782 bool canceled = false;
783 signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
784 if (!result) {
785 // TODO handle failure
786 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
787 if (!canceled) {
788 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
789 } else {
790 Q_EMIT failed(QString());
791 }
792 wasCanceled = canceled;
793 return QVector<MessageComposer::Composer *>();
794 }
795
796 canceled = false;
797 encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
798 if (!result) {
799 // TODO handle failure
800 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
801 if (!canceled) {
802 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
803 } else {
804 Q_EMIT failed(QString());
805 }
806
807 wasCanceled = canceled;
808 return QVector<MessageComposer::Composer *>();
809 }
810
811 // No encryption or signing is needed
812 if (!signSomething && !encryptSomething) {
813 return QVector<MessageComposer::Composer *>() << new MessageComposer::Composer();
814 }
815
816 const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
817 if (kpgpResult == Kleo::Canceled) {
818 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
819 return QVector<MessageComposer::Composer *>();
820 } else if (kpgpResult != Kleo::Ok) {
821 // TODO handle failure
822 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
823 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
824 return QVector<MessageComposer::Composer *>();
825 }
826 qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys:";
827
828 QVector<MessageComposer::Composer *> composers;
829
830 if (encryptSomething || signSomething) {
831 Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
832 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
833 concreteFormat = concreteCryptoMessageFormats[i];
834 if (keyResolver->encryptionItems(concreteFormat).empty()) {
835 continue;
836 }
837
838 if (!(concreteFormat & m_cryptoMessageFormat)) {
839 continue;
840 }
841
842 auto composer = new MessageComposer::Composer;
843
844 if (encryptSomething) {
845 std::vector<Kleo::KeyResolver::SplitInfo> encData = keyResolver->encryptionItems(concreteFormat);
846 std::vector<Kleo::KeyResolver::SplitInfo>::iterator it;
847 auto end(encData.end());
848 QVector<QPair<QStringList, std::vector<GpgME::Key>>> data;
849 data.reserve(encData.size());
850 for (it = encData.begin(); it != end; ++it) {
851 QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
852 data.append(p);
853 qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
854 }
855 composer->setEncryptionKeys(data);
856 if (concreteFormat & Kleo::OpenPGPMIMEFormat && id.autocryptEnabled()) {
857 composer->setAutocryptEnabled(id.autocryptEnabled());
858 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
859 QTemporaryDir dir;
860 bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
861 if (specialGnupgHome) {
862 dir.setAutoRemove(false);
863 composer->setGnupgHome(dir.path());
864 }
865 }
866 }
867
868 if (signSomething) {
869 // find signing keys for this format
870 std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
871 composer->setSigningKeys(signingKeys);
872 }
873
874 composer->setMessageCryptoFormat(concreteFormat);
875 composer->setSignAndEncrypt(signSomething, encryptSomething);
876
877 composers.append(composer);
878 }
879 } else {
880 auto composer = new MessageComposer::Composer;
881 composers.append(composer);
882 // If we canceled sign or encrypt be sure to change status in attachment.
883 markAllAttachmentsForSigning(false);
884 markAllAttachmentsForEncryption(false);
885 }
886
887 if (composers.isEmpty() && (signSomething || encryptSomething)) {
888 Q_ASSERT_X(false, "ComposerViewBase::fillCryptoInfo", "No concrete sign or encrypt method selected");
889 }
890
891 return composers;
892 }
893
fillGlobalPart(MessageComposer::GlobalPart * globalPart)894 void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
895 {
896 globalPart->setParentWidgetForGui(m_parentWidget);
897 globalPart->setCharsets(m_charsets);
898 globalPart->setMDNRequested(m_mdnRequested);
899 globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
900 }
901
fillInfoPart(MessageComposer::InfoPart * infoPart,ComposerViewBase::RecipientExpansion expansion)902 void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
903 {
904 // TODO splitAddressList and expandAliases ugliness should be handled by a
905 // special AddressListEdit widget... (later: see RecipientsEditor)
906
907 if (m_fccCombo) {
908 infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
909 } else {
910 if (m_fccCollection.isValid()) {
911 infoPart->setFcc(QString::number(m_fccCollection.id()));
912 }
913 }
914
915 infoPart->setTransportId(m_transport->currentTransportId());
916 if (expansion == UseExpandedRecipients) {
917 infoPart->setFrom(mExpandedFrom);
918 infoPart->setTo(mExpandedTo);
919 infoPart->setCc(mExpandedCc);
920 infoPart->setBcc(mExpandedBcc);
921 infoPart->setReplyTo(mExpandedReplyTo);
922 } else {
923 infoPart->setFrom(from());
924 infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
925 infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
926 infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
927 infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
928 }
929 infoPart->setSubject(subject());
930 infoPart->setUserAgent(QStringLiteral("KMail"));
931 infoPart->setUrgent(m_urgent);
932
933 if (m_msg->inReplyTo()) {
934 infoPart->setInReplyTo(m_msg->inReplyTo()->asUnicodeString());
935 }
936
937 if (m_msg->references()) {
938 infoPart->setReferences(m_msg->references()->asUnicodeString());
939 }
940
941 KMime::Headers::Base::List extras;
942 if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
943 extras << hdr;
944 }
945 if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
946 extras << hdr;
947 }
948 if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
949 extras << hdr;
950 }
951 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
952 extras << hdr;
953 }
954 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
955 extras << hdr;
956 }
957 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
958 extras << hdr;
959 }
960 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
961 extras << hdr;
962 }
963 if (auto hdr = m_msg->organization(false)) {
964 extras << hdr;
965 }
966 if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
967 extras << hdr;
968 }
969 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
970 extras << hdr;
971 }
972 if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
973 extras << hdr;
974 }
975 if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
976 extras << hdr;
977 }
978 if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
979 extras << hdr;
980 }
981 if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
982 extras << hdr;
983 }
984 if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
985 extras << hdr;
986 }
987 if (auto hdr = m_msg->headerByType("X-Face")) {
988 extras << hdr;
989 }
990 if (auto hdr = m_msg->headerByType("Face")) {
991 extras << hdr;
992 }
993 if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
994 extras << hdr;
995 }
996 if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
997 extras << hdr;
998 }
999 if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1000 extras << hdr;
1001 }
1002
1003 infoPart->setExtraHeaders(extras);
1004 }
1005
slotSendComposeResult(KJob * job)1006 void ComposerViewBase::slotSendComposeResult(KJob *job)
1007 {
1008 Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1009 auto composer = static_cast<MessageComposer::Composer *>(job);
1010 if (composer->error() != MessageComposer::Composer::NoError) {
1011 qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1012 }
1013
1014 if (composer->error() == MessageComposer::Composer::NoError) {
1015 Q_ASSERT(m_composers.contains(composer));
1016 // The messages were composed successfully.
1017 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1018 const int numberOfMessage(composer->resultMessages().size());
1019 for (int i = 0; i < numberOfMessage; ++i) {
1020 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1021 queueMessage(composer->resultMessages().at(i), composer);
1022 } else {
1023 saveMessage(composer->resultMessages().at(i), mSaveIn);
1024 }
1025 }
1026 saveRecentAddresses(composer->resultMessages().at(0));
1027 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1028 // The job warned the user about something, and the user chose to return
1029 // to the message. Nothing to do.
1030 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1031 Q_EMIT failed(i18n("Job cancelled by the user"));
1032 } else {
1033 qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1034 QString msg;
1035 if (composer->error() == MessageComposer::Composer::BugError) {
1036 msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1037 } else {
1038 msg = i18n("Could not compose message: %1", job->errorString());
1039 }
1040 Q_EMIT failed(msg);
1041 }
1042
1043 if (!composer->gnupgHome().isEmpty()) {
1044 QDir dir(composer->gnupgHome());
1045 dir.removeRecursively();
1046 }
1047
1048 m_composers.removeAll(composer);
1049 }
1050
saveRecentAddresses(const KMime::Message::Ptr & msg)1051 void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1052 {
1053 KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1054 const QVector<QByteArray> toAddresses = msg->to()->addresses();
1055 for (const QByteArray &address : toAddresses) {
1056 PimCommon::RecentAddresses::self(config)->add(QLatin1String(address));
1057 }
1058 const QVector<QByteArray> ccAddresses = msg->cc()->addresses();
1059 for (const QByteArray &address : ccAddresses) {
1060 PimCommon::RecentAddresses::self(config)->add(QLatin1String(address));
1061 }
1062 const QVector<QByteArray> bccAddresses = msg->bcc()->addresses();
1063 for (const QByteArray &address : bccAddresses) {
1064 PimCommon::RecentAddresses::self(config)->add(QLatin1String(address));
1065 }
1066 }
1067
queueMessage(const KMime::Message::Ptr & message,MessageComposer::Composer * composer)1068 void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
1069 {
1070 const MessageComposer::InfoPart *infoPart = composer->infoPart();
1071 auto qjob = new MailTransport::MessageQueueJob(this);
1072 qjob->setMessage(message);
1073 qjob->transportAttribute().setTransportId(infoPart->transportId());
1074 if (mSendMethod == MessageComposer::MessageSender::SendLater) {
1075 qjob->dispatchModeAttribute().setDispatchMode(MailTransport::DispatchModeAttribute::Manual);
1076 }
1077
1078 if (message->hasHeader("X-KMail-FccDisabled")) {
1079 qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::Delete);
1080 } else if (!infoPart->fcc().isEmpty()) {
1081 qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToCollection);
1082
1083 const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
1084 qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
1085 } else {
1086 qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection);
1087 }
1088
1089 MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
1090 if (transport && transport->specifySenderOverwriteAddress()) {
1091 qjob->addressAttribute().setFrom(
1092 KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(transport->senderOverwriteAddress())));
1093 } else {
1094 qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
1095 }
1096 // if this header is not empty, it contains the real recipient of the message, either the primary or one of the
1097 // secondary recipients. so we set that to the transport job, while leaving the message itself alone.
1098 if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
1099 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(realTo->asUnicodeString().split(QLatin1Char('%'))));
1100 message->removeHeader("X-KMail-EncBccRecipients");
1101 message->assemble();
1102 qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
1103 } else {
1104 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->to()));
1105 qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->cc()));
1106 qjob->addressAttribute().setBcc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->bcc()));
1107 }
1108 if (m_requestDeleveryConfirmation) {
1109 qjob->addressAttribute().setDeliveryStatusNotification(true);
1110 }
1111 MessageComposer::Util::addSendReplyForwardAction(message, qjob);
1112 MessageCore::StringUtil::removePrivateHeaderFields(message, false);
1113
1114 MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1115 message->assemble();
1116 connect(qjob, &MailTransport::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
1117 m_pendingQueueJobs++;
1118 qjob->start();
1119
1120 qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
1121 }
1122
slotQueueResult(KJob * job)1123 void ComposerViewBase::slotQueueResult(KJob *job)
1124 {
1125 m_pendingQueueJobs--;
1126 auto qjob = static_cast<MailTransport::MessageQueueJob *>(job);
1127 qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
1128 Q_ASSERT(m_pendingQueueJobs >= 0);
1129
1130 if (job->error()) {
1131 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
1132 // There is not much we can do now, since all the MessageQueueJobs have been
1133 // started. So just wait for them to finish.
1134 // TODO show a message box or something
1135 QString msg = i18n("There were problems trying to queue the message for sending: %1", job->errorString());
1136
1137 if (m_pendingQueueJobs == 0) {
1138 Q_EMIT failed(msg);
1139 return;
1140 }
1141 }
1142
1143 if (m_pendingQueueJobs == 0) {
1144 addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
1145 Q_EMIT sentSuccessfully(-1);
1146 }
1147 }
1148
initAutoSave()1149 void ComposerViewBase::initAutoSave()
1150 {
1151 qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
1152
1153 // Ensure that the autosave directory exists.
1154 QDir dataDirectory(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/"));
1155 if (!dataDirectory.exists(QStringLiteral("autosave"))) {
1156 qCDebug(MESSAGECOMPOSER_LOG) << "Creating autosave directory.";
1157 dataDirectory.mkdir(QStringLiteral("autosave"));
1158 }
1159
1160 // Construct a file name
1161 if (m_autoSaveUUID.isEmpty()) {
1162 m_autoSaveUUID = QUuid::createUuid().toString();
1163 }
1164
1165 updateAutoSave();
1166 }
1167
followUpCollection() const1168 Akonadi::Collection ComposerViewBase::followUpCollection() const
1169 {
1170 return mFollowUpCollection;
1171 }
1172
setFollowUpCollection(const Akonadi::Collection & followUpCollection)1173 void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1174 {
1175 mFollowUpCollection = followUpCollection;
1176 }
1177
followUpDate() const1178 QDate ComposerViewBase::followUpDate() const
1179 {
1180 return mFollowUpDate;
1181 }
1182
setFollowUpDate(const QDate & followUpDate)1183 void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1184 {
1185 mFollowUpDate = followUpDate;
1186 }
1187
dictionary() const1188 Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1189 {
1190 return m_dictionary;
1191 }
1192
setDictionary(Sonnet::DictionaryComboBox * dictionary)1193 void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1194 {
1195 m_dictionary = dictionary;
1196 }
1197
updateAutoSave()1198 void ComposerViewBase::updateAutoSave()
1199 {
1200 if (m_autoSaveInterval == 0) {
1201 delete m_autoSaveTimer;
1202 m_autoSaveTimer = nullptr;
1203 } else {
1204 if (!m_autoSaveTimer) {
1205 m_autoSaveTimer = new QTimer(this);
1206 if (m_parentWidget) {
1207 connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
1208 } else {
1209 connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
1210 }
1211 }
1212 m_autoSaveTimer->start(m_autoSaveInterval);
1213 }
1214 }
1215
cleanupAutoSave()1216 void ComposerViewBase::cleanupAutoSave()
1217 {
1218 delete m_autoSaveTimer;
1219 m_autoSaveTimer = nullptr;
1220 if (!m_autoSaveUUID.isEmpty()) {
1221 qCDebug(MESSAGECOMPOSER_LOG) << "deleting autosave files" << m_autoSaveUUID;
1222
1223 // Delete the autosave files
1224 QDir autoSaveDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/autosave"));
1225
1226 // Filter out only this composer window's autosave files
1227 const QStringList autoSaveFilter{m_autoSaveUUID + QLatin1String("*")};
1228 autoSaveDir.setNameFilters(autoSaveFilter);
1229
1230 // Return the files to be removed
1231 const QStringList autoSaveFiles = autoSaveDir.entryList();
1232 qCDebug(MESSAGECOMPOSER_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
1233
1234 // Delete each file
1235 for (const QString &file : autoSaveFiles) {
1236 autoSaveDir.remove(file);
1237 }
1238 m_autoSaveUUID.clear();
1239 }
1240 }
1241
1242 //-----------------------------------------------------------------------------
autoSaveMessage()1243 void ComposerViewBase::autoSaveMessage()
1244 {
1245 qCDebug(MESSAGECOMPOSER_LOG) << "Autosaving message";
1246
1247 if (m_autoSaveTimer) {
1248 m_autoSaveTimer->stop();
1249 }
1250
1251 if (!m_composers.isEmpty()) {
1252 // This may happen if e.g. the autosave timer calls applyChanges.
1253 qCDebug(MESSAGECOMPOSER_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
1254 return;
1255 }
1256
1257 const KIdentityManagement::Identity &id = m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1258 auto composer = new Composer();
1259 fillComposer(composer);
1260 composer->setAutoSave(true);
1261 composer->setAutocryptEnabled(id.autocryptEnabled());
1262 m_composers.append(composer);
1263 connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
1264 composer->start();
1265 }
1266
setAutoSaveFileName(const QString & fileName)1267 void ComposerViewBase::setAutoSaveFileName(const QString &fileName)
1268 {
1269 m_autoSaveUUID = fileName;
1270
1271 Q_EMIT modified(true);
1272 }
1273
slotAutoSaveComposeResult(KJob * job)1274 void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
1275 {
1276 using MessageComposer::Composer;
1277
1278 Q_ASSERT(dynamic_cast<Composer *>(job));
1279 auto composer = static_cast<Composer *>(job);
1280
1281 if (composer->error() == Composer::NoError) {
1282 Q_ASSERT(m_composers.contains(composer));
1283
1284 // The messages were composed successfully. Only save the first message, there should
1285 // only be one anyway, since crypto is disabled.
1286 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1287 writeAutoSaveToDisk(composer->resultMessages().constFirst());
1288 Q_ASSERT(composer->resultMessages().size() == 1);
1289
1290 if (m_autoSaveInterval > 0) {
1291 updateAutoSave();
1292 }
1293 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1294 // The job warned the user about something, and the user chose to return
1295 // to the message. Nothing to do.
1296 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1297 Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
1298 } else {
1299 qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
1300 Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
1301 }
1302
1303 m_composers.removeAll(composer);
1304 }
1305
writeAutoSaveToDisk(const KMime::Message::Ptr & message)1306 void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1307 {
1308 const QString autosavePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/autosave/");
1309 QDir().mkpath(autosavePath);
1310 const QString filename = autosavePath + m_autoSaveUUID;
1311 QSaveFile file(filename);
1312 QString errorMessage;
1313 qCDebug(MESSAGECOMPOSER_LOG) << "Writing message to disk as" << filename;
1314
1315 if (file.open(QIODevice::WriteOnly)) {
1316 file.setPermissions(QFile::ReadUser | QFile::WriteUser);
1317
1318 if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
1319 errorMessage = i18n("Could not write all data to file.");
1320 } else {
1321 if (!file.commit()) {
1322 errorMessage = i18n("Could not finalize the file.");
1323 }
1324 }
1325 } else {
1326 errorMessage = i18n("Could not open file.");
1327 }
1328
1329 if (!errorMessage.isEmpty()) {
1330 qCWarning(MESSAGECOMPOSER_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
1331 if (!m_autoSaveErrorShown) {
1332 KMessageBox::sorry(m_parentWidget,
1333 i18n("Autosaving the message as %1 failed.\n"
1334 "%2\n"
1335 "Reason: %3",
1336 filename,
1337 errorMessage,
1338 file.errorString()),
1339 i18n("Autosaving Message Failed"));
1340
1341 // Error dialog shown, hide the errors the next time
1342 m_autoSaveErrorShown = true;
1343 }
1344 } else {
1345 // No error occurred, the next error should be shown again
1346 m_autoSaveErrorShown = false;
1347 }
1348 file.commit();
1349 message->clear();
1350 }
1351
saveMessage(const KMime::Message::Ptr & message,MessageComposer::MessageSender::SaveIn saveIn)1352 void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
1353 {
1354 Akonadi::Collection target;
1355 const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
1356 message->date()->setDateTime(QDateTime::currentDateTime());
1357 if (!identity.isNull()) {
1358 if (auto header = message->headerByType("X-KMail-Fcc")) {
1359 const int sentCollectionId = header->asUnicodeString().toInt();
1360 if (identity.fcc() == QString::number(sentCollectionId)) {
1361 message->removeHeader("X-KMail-Fcc");
1362 }
1363 }
1364 }
1365 MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1366
1367 message->assemble();
1368
1369 Akonadi::Item item;
1370 item.setMimeType(QStringLiteral("message/rfc822"));
1371 item.setPayload(message);
1372 Akonadi::MessageFlags::copyMessageFlags(*message, item);
1373
1374 if (!identity.isNull()) { // we have a valid identity
1375 switch (saveIn) {
1376 case MessageComposer::MessageSender::SaveInTemplates:
1377 if (!identity.templates().isEmpty()) { // the user has specified a custom templates collection
1378 target = Akonadi::Collection(identity.templates().toLongLong());
1379 }
1380 break;
1381 case MessageComposer::MessageSender::SaveInDrafts:
1382 if (!identity.drafts().isEmpty()) { // the user has specified a custom drafts collection
1383 target = Akonadi::Collection(identity.drafts().toLongLong());
1384 }
1385 break;
1386 case MessageComposer::MessageSender::SaveInOutbox: // We don't define save outbox in identity
1387 target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Outbox);
1388 break;
1389 case MessageComposer::MessageSender::SaveInNone:
1390 break;
1391 }
1392
1393 auto saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
1394 saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
1395 QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
1396 } else {
1397 // preinitialize with the default collections
1398 target = defaultSpecialTarget();
1399 auto create = new Akonadi::ItemCreateJob(item, target, this);
1400 connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1401 ++m_pendingQueueJobs;
1402 }
1403 }
1404
slotSaveMessage(KJob * job)1405 void ComposerViewBase::slotSaveMessage(KJob *job)
1406 {
1407 Akonadi::Collection target;
1408 auto item = job->property("Akonadi::Item").value<Akonadi::Item>();
1409 if (job->error()) {
1410 target = defaultSpecialTarget();
1411 } else {
1412 const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
1413 if (fetchJob->collections().isEmpty()) {
1414 target = defaultSpecialTarget();
1415 } else {
1416 target = fetchJob->collections().at(0);
1417 }
1418 }
1419 auto create = new Akonadi::ItemCreateJob(item, target, this);
1420 connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1421 ++m_pendingQueueJobs;
1422 }
1423
defaultSpecialTarget() const1424 Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1425 {
1426 Akonadi::Collection target;
1427 switch (mSaveIn) {
1428 case MessageComposer::MessageSender::SaveInNone:
1429 break;
1430 case MessageComposer::MessageSender::SaveInDrafts:
1431 target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Drafts);
1432 break;
1433 case MessageComposer::MessageSender::SaveInTemplates:
1434 target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Templates);
1435 break;
1436 case MessageComposer::MessageSender::SaveInOutbox:
1437 target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Outbox);
1438 break;
1439 }
1440
1441 return target;
1442 }
1443
slotCreateItemResult(KJob * job)1444 void ComposerViewBase::slotCreateItemResult(KJob *job)
1445 {
1446 --m_pendingQueueJobs;
1447 qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
1448 Q_ASSERT(m_pendingQueueJobs >= 0);
1449
1450 if (job->error()) {
1451 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
1452 Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
1453 return;
1454 }
1455
1456 Akonadi::Item::Id id = -1;
1457 if (mSendLaterInfo) {
1458 auto createJob = static_cast<Akonadi::ItemCreateJob *>(job);
1459 const Akonadi::Item item = createJob->item();
1460 if (item.isValid()) {
1461 id = item.id();
1462 addSendLaterItem(item);
1463 }
1464 }
1465
1466 if (m_pendingQueueJobs == 0) {
1467 Q_EMIT sentSuccessfully(id);
1468 }
1469 }
1470
addAttachment(const QUrl & url,const QString & comment,bool sync)1471 void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
1472 {
1473 Q_UNUSED(comment)
1474 qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
1475 if (sync) {
1476 m_attachmentController->addAttachmentUrlSync(url);
1477 } else {
1478 m_attachmentController->addAttachment(url);
1479 }
1480 }
1481
addAttachment(const QString & name,const QString & filename,const QString & charset,const QByteArray & data,const QByteArray & mimeType)1482 void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1483 {
1484 MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
1485 if (!data.isEmpty()) {
1486 attachment->setName(name);
1487 attachment->setFileName(filename);
1488 attachment->setData(data);
1489 attachment->setCharset(charset.toLatin1());
1490 attachment->setMimeType(mimeType);
1491 // TODO what about the other fields?
1492
1493 m_attachmentController->addAttachment(attachment);
1494 }
1495 }
1496
addAttachmentPart(KMime::Content * partToAttach)1497 void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1498 {
1499 MessageCore::AttachmentPart::Ptr part(new MessageCore::AttachmentPart);
1500 if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
1501 // if it is a digest or a full message, use the encodedContent() of the attachment,
1502 // which already has the proper headers
1503 part->setData(partToAttach->encodedContent());
1504 } else {
1505 part->setData(partToAttach->decodedContent());
1506 }
1507 part->setMimeType(partToAttach->contentType(false)->mimeType());
1508 if (auto cd = partToAttach->contentDescription(false)) {
1509 part->setDescription(cd->asUnicodeString());
1510 }
1511 if (auto ct = partToAttach->contentType(false)) {
1512 if (ct->hasParameter(QStringLiteral("name"))) {
1513 part->setName(ct->parameter(QStringLiteral("name")));
1514 }
1515 }
1516 if (auto cd = partToAttach->contentDisposition(false)) {
1517 part->setFileName(cd->filename());
1518 part->setInline(cd->disposition() == KMime::Headers::CDinline);
1519 }
1520 if (part->name().isEmpty() && !part->fileName().isEmpty()) {
1521 part->setName(part->fileName());
1522 }
1523 if (part->fileName().isEmpty() && !part->name().isEmpty()) {
1524 part->setFileName(part->name());
1525 }
1526 m_attachmentController->addAttachment(part);
1527 }
1528
1529
fillComposer(MessageComposer::Composer * composer)1530 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1531 {
1532 fillComposer(composer, UseUnExpandedRecipients, false);
1533 }
1534
fillComposer(MessageComposer::Composer * composer,ComposerViewBase::RecipientExpansion expansion,bool autoresize)1535 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
1536 {
1537 fillGlobalPart(composer->globalPart());
1538 m_editor->fillComposerTextPart(composer->textPart());
1539 fillInfoPart(composer->infoPart(), expansion);
1540 if (m_attachmentModel) {
1541 composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
1542 }
1543 }
1544
1545 //-----------------------------------------------------------------------------
to() const1546 QString ComposerViewBase::to() const
1547 {
1548 if (m_recipientsEditor) {
1549 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1550 }
1551 return {};
1552 }
1553
1554 //-----------------------------------------------------------------------------
cc() const1555 QString ComposerViewBase::cc() const
1556 {
1557 if (m_recipientsEditor) {
1558 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
1559 }
1560 return {};
1561 }
1562
1563 //-----------------------------------------------------------------------------
bcc() const1564 QString ComposerViewBase::bcc() const
1565 {
1566 if (m_recipientsEditor) {
1567 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1568 }
1569 return {};
1570 }
1571
from() const1572 QString ComposerViewBase::from() const
1573 {
1574 return MessageComposer::Util::cleanedUpHeaderString(m_from);
1575 }
1576
replyTo() const1577 QString ComposerViewBase::replyTo() const
1578 {
1579 if (m_recipientsEditor) {
1580 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1581 }
1582 return {};
1583 }
1584
subject() const1585 QString ComposerViewBase::subject() const
1586 {
1587 return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1588 }
1589
setParentWidgetForGui(QWidget * w)1590 void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1591 {
1592 m_parentWidget = w;
1593 }
1594
setAttachmentController(MessageComposer::AttachmentControllerBase * controller)1595 void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1596 {
1597 m_attachmentController = controller;
1598 }
1599
attachmentController()1600 MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1601 {
1602 return m_attachmentController;
1603 }
1604
setAttachmentModel(MessageComposer::AttachmentModel * model)1605 void ComposerViewBase::setAttachmentModel(MessageComposer::AttachmentModel *model)
1606 {
1607 m_attachmentModel = model;
1608 }
1609
attachmentModel()1610 MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1611 {
1612 return m_attachmentModel;
1613 }
1614
setRecipientsEditor(MessageComposer::RecipientsEditor * recEditor)1615 void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1616 {
1617 m_recipientsEditor = recEditor;
1618 }
1619
recipientsEditor()1620 MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1621 {
1622 return m_recipientsEditor;
1623 }
1624
setSignatureController(MessageComposer::SignatureController * sigController)1625 void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1626 {
1627 m_signatureController = sigController;
1628 }
1629
signatureController()1630 MessageComposer::SignatureController *ComposerViewBase::signatureController()
1631 {
1632 return m_signatureController;
1633 }
1634
setIdentityCombo(KIdentityManagement::IdentityCombo * identCombo)1635 void ComposerViewBase::setIdentityCombo(KIdentityManagement::IdentityCombo *identCombo)
1636 {
1637 m_identityCombo = identCombo;
1638 }
1639
identityCombo()1640 KIdentityManagement::IdentityCombo *ComposerViewBase::identityCombo()
1641 {
1642 return m_identityCombo;
1643 }
1644
updateRecipients(const KIdentityManagement::Identity & ident,const KIdentityManagement::Identity & oldIdent,MessageComposer::Recipient::Type type)1645 void ComposerViewBase::updateRecipients(const KIdentityManagement::Identity &ident,
1646 const KIdentityManagement::Identity &oldIdent,
1647 MessageComposer::Recipient::Type type)
1648 {
1649 QString oldIdentList;
1650 QString newIdentList;
1651 if (type == MessageComposer::Recipient::Bcc) {
1652 oldIdentList = oldIdent.bcc();
1653 newIdentList = ident.bcc();
1654 } else if (type == MessageComposer::Recipient::Cc) {
1655 oldIdentList = oldIdent.cc();
1656 newIdentList = ident.cc();
1657 } else if (type == MessageComposer::Recipient::ReplyTo) {
1658 oldIdentList = oldIdent.replyToAddr();
1659 newIdentList = ident.replyToAddr();
1660 } else {
1661 return;
1662 }
1663
1664 if (oldIdentList != newIdentList) {
1665 const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
1666 for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1667 m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1668 }
1669
1670 const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
1671 for (const KMime::Types::Mailbox &recipient : newRecipients) {
1672 m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1673 }
1674 m_recipientsEditor->setFocusBottom();
1675 }
1676 }
1677
identityChanged(const KIdentityManagement::Identity & ident,const KIdentityManagement::Identity & oldIdent,bool msgCleared)1678 void ComposerViewBase::identityChanged(const KIdentityManagement::Identity &ident, const KIdentityManagement::Identity &oldIdent, bool msgCleared)
1679 {
1680 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1681 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1682 updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1683
1684 KIdentityManagement::Signature oldSig = const_cast<KIdentityManagement::Identity &>(oldIdent).signature();
1685 KIdentityManagement::Signature newSig = const_cast<KIdentityManagement::Identity &>(ident).signature();
1686 // replace existing signatures
1687 const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1688 // Just append the signature if there was no old signature
1689 if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1690 signatureController()->applySignature(newSig);
1691 }
1692 const QString vcardFileName = ident.vCardFile();
1693 attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1694 attachmentController()->setAttachOwnVcard(ident.attachVcard());
1695
1696 m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1697 }
1698
setEditor(MessageComposer::RichTextComposerNg * editor)1699 void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1700 {
1701 m_editor = editor;
1702 m_editor->document()->setModified(false);
1703 }
1704
editor() const1705 MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1706 {
1707 return m_editor;
1708 }
1709
setTransportCombo(MailTransport::TransportComboBox * transpCombo)1710 void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1711 {
1712 m_transport = transpCombo;
1713 }
1714
transportComboBox() const1715 MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1716 {
1717 return m_transport;
1718 }
1719
setIdentityManager(KIdentityManagement::IdentityManager * identMan)1720 void ComposerViewBase::setIdentityManager(KIdentityManagement::IdentityManager *identMan)
1721 {
1722 m_identMan = identMan;
1723 }
1724
identityManager()1725 KIdentityManagement::IdentityManager *ComposerViewBase::identityManager()
1726 {
1727 return m_identMan;
1728 }
1729
setFcc(const Akonadi::Collection & fccCollection)1730 void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1731 {
1732 if (m_fccCombo) {
1733 m_fccCombo->setDefaultCollection(fccCollection);
1734 } else {
1735 m_fccCollection = fccCollection;
1736 }
1737 auto const checkFccCollectionJob = new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
1738 connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1739 }
1740
slotFccCollectionCheckResult(KJob * job)1741 void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1742 {
1743 if (job->error()) {
1744 qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1745 const Akonadi::Collection sentMailCol = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::SentMail);
1746 if (m_fccCombo) {
1747 m_fccCombo->setDefaultCollection(sentMailCol);
1748 } else {
1749 m_fccCollection = sentMailCol;
1750 }
1751 }
1752 }
1753
setFccCombo(Akonadi::CollectionComboBox * fcc)1754 void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1755 {
1756 m_fccCombo = fcc;
1757 }
1758
fccCombo() const1759 Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1760 {
1761 return m_fccCombo;
1762 }
1763
setFrom(const QString & from)1764 void ComposerViewBase::setFrom(const QString &from)
1765 {
1766 m_from = from;
1767 }
1768
setSubject(const QString & subject)1769 void ComposerViewBase::setSubject(const QString &subject)
1770 {
1771 m_subject = subject;
1772 if (mSendLaterInfo) {
1773 mSendLaterInfo->setSubject(m_subject);
1774 mSendLaterInfo->setTo(to());
1775 }
1776 }
1777
setAutoSaveInterval(int interval)1778 void ComposerViewBase::setAutoSaveInterval(int interval)
1779 {
1780 m_autoSaveInterval = interval;
1781 }
1782
setCryptoOptions(bool sign,bool encrypt,Kleo::CryptoMessageFormat format,bool neverEncryptDrafts)1783 void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1784 {
1785 m_sign = sign;
1786 m_encrypt = encrypt;
1787 m_cryptoMessageFormat = format;
1788 m_neverEncrypt = neverEncryptDrafts;
1789 }
1790
setCharsets(const QVector<QByteArray> & charsets)1791 void ComposerViewBase::setCharsets(const QVector<QByteArray> &charsets)
1792 {
1793 m_charsets = charsets;
1794 }
1795
setMDNRequested(bool mdnRequested)1796 void ComposerViewBase::setMDNRequested(bool mdnRequested)
1797 {
1798 m_mdnRequested = mdnRequested;
1799 }
1800
setUrgent(bool urgent)1801 void ComposerViewBase::setUrgent(bool urgent)
1802 {
1803 m_urgent = urgent;
1804 }
1805
autoSaveInterval() const1806 int ComposerViewBase::autoSaveInterval() const
1807 {
1808 return m_autoSaveInterval;
1809 }
1810
1811 //-----------------------------------------------------------------------------
collectImages(KMime::Content * root)1812 void ComposerViewBase::collectImages(KMime::Content *root)
1813 {
1814 if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1815 KMime::Content *parentnode = n->parent();
1816 if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1817 KMime::Content *node = MessageCore::NodeHelper::nextSibling(n);
1818 while (node) {
1819 if (node->contentType()->isImage()) {
1820 qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1821 QImage img;
1822 img.loadFromData(node->decodedContent());
1823 m_editor->composerControler()->composerImages()->loadImage(
1824 img,
1825 QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1826 node->contentType()->name());
1827 }
1828 node = MessageCore::NodeHelper::nextSibling(node);
1829 }
1830 }
1831 }
1832 }
1833
1834 //-----------------------------------------------------------------------------
inlineSigningEncryptionSelected() const1835 bool ComposerViewBase::inlineSigningEncryptionSelected() const
1836 {
1837 if (!m_sign && !m_encrypt) {
1838 return false;
1839 }
1840 return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1841 }
1842
hasMissingAttachments(const QStringList & attachmentKeywords)1843 bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1844 {
1845 if (attachmentKeywords.isEmpty()) {
1846 return false;
1847 }
1848 if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1849 return false;
1850 }
1851
1852 return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1853 }
1854
checkForMissingAttachments(const QStringList & attachmentKeywords)1855 ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1856 {
1857 if (!hasMissingAttachments(attachmentKeywords)) {
1858 return NoMissingAttachmentFound;
1859 }
1860 int rc = KMessageBox::warningYesNoCancel(m_editor,
1861 i18n("The message you have composed seems to refer to an "
1862 "attached file but you have not attached anything.\n"
1863 "Do you want to attach a file to your message?"),
1864 i18n("File Attachment Reminder"),
1865 KGuiItem(i18n("&Attach File...")),
1866 KGuiItem(i18n("&Send as Is")));
1867 if (rc == KMessageBox::Cancel) {
1868 return FoundMissingAttachmentAndCancel;
1869 }
1870 if (rc == KMessageBox::Yes) {
1871 m_attachmentController->showAddAttachmentFileDialog();
1872 return FoundMissingAttachmentAndAddedAttachment;
1873 }
1874
1875 return FoundMissingAttachmentAndSending;
1876 }
1877
markAllAttachmentsForSigning(bool sign)1878 void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1879 {
1880 if (m_attachmentModel) {
1881 const auto attachments = m_attachmentModel->attachments();
1882 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1883 attachment->setSigned(sign);
1884 }
1885 }
1886 }
1887
markAllAttachmentsForEncryption(bool encrypt)1888 void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1889 {
1890 if (m_attachmentModel) {
1891 const auto attachments = m_attachmentModel->attachments();
1892 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1893 attachment->setEncrypted(encrypt);
1894 }
1895 }
1896 }
1897
determineWhetherToSign(bool doSignCompletely,Kleo::KeyResolver * keyResolver,bool signSomething,bool & result,bool & canceled)1898 bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1899 {
1900 bool sign = false;
1901 switch (keyResolver->checkSigningPreferences(signSomething)) {
1902 case Kleo::DoIt:
1903 if (!signSomething) {
1904 markAllAttachmentsForSigning(true);
1905 return true;
1906 }
1907 sign = true;
1908 break;
1909 case Kleo::DontDoIt:
1910 sign = false;
1911 break;
1912 case Kleo::AskOpportunistic:
1913 assert(0);
1914 case Kleo::Ask: {
1915 // the user wants to be asked or has to be asked
1916 KCursorSaver saver(Qt::WaitCursor);
1917 const QString msg = i18n(
1918 "Examination of the recipient's signing preferences "
1919 "yielded that you be asked whether or not to sign "
1920 "this message.\n"
1921 "Sign this message?");
1922 switch (
1923 KMessageBox::questionYesNoCancel(m_parentWidget, msg, i18n("Sign Message?"), KGuiItem(i18nc("to sign", "&Sign")), KGuiItem(i18n("Do &Not Sign")))) {
1924 case KMessageBox::Cancel:
1925 result = false;
1926 canceled = true;
1927 return false;
1928 case KMessageBox::Yes:
1929 markAllAttachmentsForSigning(true);
1930 return true;
1931 case KMessageBox::No:
1932 markAllAttachmentsForSigning(false);
1933 return false;
1934 default:
1935 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1936 return false;
1937 }
1938 break;
1939 }
1940 case Kleo::Conflict: {
1941 // warn the user that there are conflicting signing preferences
1942 KCursorSaver saver(Qt::WaitCursor);
1943 const QString msg = i18n(
1944 "There are conflicting signing preferences "
1945 "for these recipients.\n"
1946 "Sign this message?");
1947 switch (
1948 KMessageBox::warningYesNoCancel(m_parentWidget, msg, i18n("Sign Message?"), KGuiItem(i18nc("to sign", "&Sign")), KGuiItem(i18n("Do &Not Sign")))) {
1949 case KMessageBox::Cancel:
1950 result = false;
1951 canceled = true;
1952 return false;
1953 case KMessageBox::Yes:
1954 markAllAttachmentsForSigning(true);
1955 return true;
1956 case KMessageBox::No:
1957 markAllAttachmentsForSigning(false);
1958 return false;
1959 default:
1960 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1961 return false;
1962 }
1963 break;
1964 }
1965 case Kleo::Impossible: {
1966 KCursorSaver saver(Qt::WaitCursor);
1967 const QString msg = i18n(
1968 "You have requested to sign this message, "
1969 "but no valid signing keys have been configured "
1970 "for this identity.");
1971 if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18n("Send Unsigned?"), KGuiItem(i18n("Send &Unsigned"))) == KMessageBox::Cancel) {
1972 result = false;
1973 return false;
1974 } else {
1975 markAllAttachmentsForSigning(false);
1976 return false;
1977 }
1978 }
1979 }
1980
1981 if (!sign || !doSignCompletely) {
1982 if (MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned()) {
1983 KCursorSaver saver(Qt::WaitCursor);
1984 const QString msg = sign && !doSignCompletely ? i18n(
1985 "Some parts of this message will not be signed.\n"
1986 "Sending only partially signed messages might violate site policy.\n"
1987 "Sign all parts instead?") // oh, I hate this...
1988 : i18n(
1989 "This message will not be signed.\n"
1990 "Sending unsigned message might violate site policy.\n"
1991 "Sign message instead?"); // oh, I hate this...
1992 const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
1993 switch (
1994 KMessageBox::warningYesNoCancel(m_parentWidget, msg, i18n("Unsigned-Message Warning"), KGuiItem(buttonText), KGuiItem(i18n("Send &As Is")))) {
1995 case KMessageBox::Cancel:
1996 result = false;
1997 canceled = true;
1998 return false;
1999 case KMessageBox::Yes:
2000 markAllAttachmentsForSigning(true);
2001 return true;
2002 case KMessageBox::No:
2003 return sign || doSignCompletely;
2004 default:
2005 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2006 return false;
2007 }
2008 }
2009 }
2010 return sign || doSignCompletely;
2011 }
2012
determineWhetherToEncrypt(bool doEncryptCompletely,Kleo::KeyResolver * keyResolver,bool encryptSomething,bool signSomething,bool & result,bool & canceled)2013 bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2014 Kleo::KeyResolver *keyResolver,
2015 bool encryptSomething,
2016 bool signSomething,
2017 bool &result,
2018 bool &canceled)
2019 {
2020 bool encrypt = false;
2021 bool opportunistic = false;
2022 switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2023 case Kleo::DoIt:
2024 if (!encryptSomething) {
2025 markAllAttachmentsForEncryption(true);
2026 return true;
2027 }
2028 encrypt = true;
2029 break;
2030 case Kleo::DontDoIt:
2031 encrypt = false;
2032 break;
2033 case Kleo::AskOpportunistic:
2034 opportunistic = true;
2035 // fall through...
2036 Q_FALLTHROUGH();
2037 case Kleo::Ask: {
2038 // the user wants to be asked or has to be asked
2039 KCursorSaver saver(Qt::WaitCursor);
2040 const QString msg = opportunistic ? i18n(
2041 "Valid trusted encryption keys were found for all recipients.\n"
2042 "Encrypt this message?")
2043 : i18n(
2044 "Examination of the recipient's encryption preferences "
2045 "yielded that you be asked whether or not to encrypt "
2046 "this message.\n"
2047 "Encrypt this message?");
2048 switch (KMessageBox::questionYesNoCancel(m_parentWidget,
2049 msg,
2050 i18n("Encrypt Message?"),
2051 KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2052 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2053 case KMessageBox::Cancel:
2054 result = false;
2055 canceled = true;
2056 return false;
2057 case KMessageBox::Yes:
2058 markAllAttachmentsForEncryption(true);
2059 return true;
2060 case KMessageBox::No:
2061 markAllAttachmentsForEncryption(false);
2062 return false;
2063 default:
2064 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2065 return false;
2066 }
2067 break;
2068 }
2069 case Kleo::Conflict: {
2070 // warn the user that there are conflicting encryption preferences
2071 KCursorSaver saver(Qt::WaitCursor);
2072 const QString msg = i18n(
2073 "There are conflicting encryption preferences "
2074 "for these recipients.\n"
2075 "Encrypt this message?");
2076 switch (KMessageBox::warningYesNoCancel(m_parentWidget, msg, i18n("Encrypt Message?"), KGuiItem(i18n("&Encrypt")), KGuiItem(i18n("Do &Not Encrypt")))) {
2077 case KMessageBox::Cancel:
2078 result = false;
2079 canceled = true;
2080 return false;
2081 case KMessageBox::Yes:
2082 markAllAttachmentsForEncryption(true);
2083 return true;
2084 case KMessageBox::No:
2085 markAllAttachmentsForEncryption(false);
2086 return false;
2087 default:
2088 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2089 return false;
2090 }
2091 break;
2092 }
2093 case Kleo::Impossible: {
2094 KCursorSaver saver(Qt::WaitCursor);
2095 const QString msg = i18n(
2096 "You have requested to encrypt this message, "
2097 "and to encrypt a copy to yourself, "
2098 "but no valid trusted encryption keys have been "
2099 "configured for this identity.");
2100 if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18n("Send Unencrypted?"), KGuiItem(i18n("Send &Unencrypted"))) == KMessageBox::Cancel) {
2101 result = false;
2102 return false;
2103 } else {
2104 markAllAttachmentsForEncryption(false);
2105 return false;
2106 }
2107 }
2108 }
2109
2110 if (!encrypt || !doEncryptCompletely) {
2111 if (MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted()) {
2112 KCursorSaver saver(Qt::WaitCursor);
2113 const QString msg = !doEncryptCompletely ? i18n(
2114 "Some parts of this message will not be encrypted.\n"
2115 "Sending only partially encrypted messages might violate "
2116 "site policy and/or leak sensitive information.\n"
2117 "Encrypt all parts instead?") // oh, I hate this...
2118 : i18n(
2119 "This message will not be encrypted.\n"
2120 "Sending unencrypted messages might violate site policy and/or "
2121 "leak sensitive information.\n"
2122 "Encrypt messages instead?"); // oh, I hate this...
2123 const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2124 switch (KMessageBox::warningYesNoCancel(m_parentWidget,
2125 msg,
2126 i18n("Unencrypted Message Warning"),
2127 KGuiItem(buttonText),
2128 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2129 case KMessageBox::Cancel:
2130 result = false;
2131 canceled = true;
2132 return false;
2133 case KMessageBox::Yes:
2134 markAllAttachmentsForEncryption(true);
2135 return true;
2136 case KMessageBox::No:
2137 return encrypt || doEncryptCompletely;
2138 default:
2139 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2140 return false;
2141 }
2142 }
2143 }
2144
2145 return encrypt || doEncryptCompletely;
2146 }
2147
setSendLaterInfo(SendLaterInfo * info)2148 void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2149 {
2150 mSendLaterInfo.reset(info);
2151 }
2152
sendLaterInfo() const2153 SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2154 {
2155 return mSendLaterInfo.get();
2156 }
2157
addFollowupReminder(const QString & messageId)2158 void ComposerViewBase::addFollowupReminder(const QString &messageId)
2159 {
2160 if (!messageId.isEmpty()) {
2161 if (mFollowUpDate.isValid()) {
2162 auto job = new MessageComposer::FollowupReminderCreateJob;
2163 job->setSubject(m_subject);
2164 job->setMessageId(messageId);
2165 job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2166 job->setFollowUpReminderDate(mFollowUpDate);
2167 job->setCollectionToDo(mFollowUpCollection);
2168 job->start();
2169 }
2170 }
2171 }
2172
addSendLaterItem(const Akonadi::Item & item)2173 void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2174 {
2175 mSendLaterInfo->setItemId(item.id());
2176
2177 auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2178 job->start();
2179 }
2180
requestDeleveryConfirmation() const2181 bool ComposerViewBase::requestDeleveryConfirmation() const
2182 {
2183 return m_requestDeleveryConfirmation;
2184 }
2185
setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)2186 void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2187 {
2188 m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2189 }
2190
msg() const2191 KMime::Message::Ptr ComposerViewBase::msg() const
2192 {
2193 return m_msg;
2194 }
2195