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