File indexing completed on 2024-06-23 05:18:27

0001 /*
0002   SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
0003   SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
0004 
0005   SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "composerviewbase.h"
0009 
0010 #include "attachment/attachmentcontrollerbase.h"
0011 #include "attachment/attachmentmodel.h"
0012 #include "composer-ng/richtextcomposerng.h"
0013 #include "composer-ng/richtextcomposersignatures.h"
0014 #include "composer.h"
0015 #include "composer/keyresolver.h"
0016 #include "composer/signaturecontroller.h"
0017 #include "draftstatus/draftstatus.h"
0018 #include "imagescaling/imagescalingutils.h"
0019 #include "job/emailaddressresolvejob.h"
0020 #include "part/globalpart.h"
0021 #include "part/infopart.h"
0022 #include "utils/kleo_util.h"
0023 #include "utils/util.h"
0024 #include "utils/util_p.h"
0025 #include <KPIMTextEdit/RichTextComposerControler>
0026 #include <KPIMTextEdit/RichTextComposerImages>
0027 
0028 #include "sendlater/sendlatercreatejob.h"
0029 #include "sendlater/sendlaterinfo.h"
0030 
0031 #include <PimCommonAkonadi/RecentAddresses>
0032 
0033 #include "settings/messagecomposersettings.h"
0034 #include <MessageComposer/RecipientsEditor>
0035 
0036 #include <KCursorSaver>
0037 #include <KIdentityManagementCore/Identity>
0038 #include <MimeTreeParser/ObjectTreeParser>
0039 #include <MimeTreeParser/SimpleObjectTreeSource>
0040 #include <Sonnet/DictionaryComboBox>
0041 
0042 #include <MessageCore/AutocryptStorage>
0043 #include <MessageCore/NodeHelper>
0044 #include <MessageCore/StringUtil>
0045 
0046 #include <Akonadi/MessageQueueJob>
0047 #include <MailTransport/TransportComboBox>
0048 #include <MailTransport/TransportManager>
0049 
0050 #include <Akonadi/CollectionComboBox>
0051 #include <Akonadi/CollectionFetchJob>
0052 #include <Akonadi/ItemCreateJob>
0053 #include <Akonadi/MessageFlags>
0054 #include <Akonadi/SpecialMailCollections>
0055 
0056 #include <KEmailAddress>
0057 #include <KIdentityManagementCore/IdentityManager>
0058 #include <KIdentityManagementWidgets/IdentityCombo>
0059 
0060 #include "messagecomposer_debug.h"
0061 
0062 #include <Libkleo/ExpiryChecker>
0063 #include <Libkleo/ExpiryCheckerSettings>
0064 
0065 #include <QGpgME/ExportJob>
0066 #include <QGpgME/ImportJob>
0067 #include <QGpgME/Protocol>
0068 #include <gpgme++/context.h>
0069 #include <gpgme++/importresult.h>
0070 
0071 #include <KLocalizedString>
0072 #include <KMessageBox>
0073 #include <QSaveFile>
0074 
0075 #include "followupreminder/followupremindercreatejob.h"
0076 #include <QDir>
0077 #include <QStandardPaths>
0078 #include <QTemporaryDir>
0079 #include <QTimer>
0080 #include <QUuid>
0081 
0082 using namespace MessageComposer;
0083 
0084 ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
0085     : QObject(parent)
0086     , m_msg(KMime::Message::Ptr(new KMime::Message))
0087     , m_parentWidget(parentGui)
0088     , m_cryptoMessageFormat(Kleo::AutoFormat)
0089     , m_autoSaveInterval(60000) // default of 1 min
0090 {
0091     m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set.
0092 
0093     initAutoSave();
0094 }
0095 
0096 ComposerViewBase::~ComposerViewBase() = default;
0097 
0098 bool ComposerViewBase::isComposing() const
0099 {
0100     return !m_composers.isEmpty();
0101 }
0102 
0103 void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg, bool allowDecryption)
0104 {
0105     if (m_attachmentModel) {
0106         const auto attachments{m_attachmentModel->attachments()};
0107         for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
0108             if (!m_attachmentModel->removeAttachment(attachment)) {
0109                 qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
0110             }
0111         }
0112     }
0113     m_msg = msg;
0114     if (m_recipientsEditor) {
0115         m_recipientsEditor->clear();
0116         bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), MessageComposer::Recipient::To);
0117         if (!resultTooManyRecipients) {
0118             resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc);
0119         }
0120         if (!resultTooManyRecipients) {
0121             resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc);
0122         }
0123         if (!resultTooManyRecipients) {
0124             resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), MessageComposer::Recipient::ReplyTo);
0125         }
0126         m_recipientsEditor->setFocusBottom();
0127 
0128         if (!resultTooManyRecipients) {
0129             // If we are loading from a draft, load unexpanded aliases as well
0130             if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-To")) {
0131                 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
0132                 for (const QString &addr : spl) {
0133                     if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::To)) {
0134                         resultTooManyRecipients = true;
0135                         qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
0136                         break;
0137                     }
0138                 }
0139             }
0140         }
0141         if (!resultTooManyRecipients) {
0142             if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
0143                 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
0144                 for (const QString &addr : spl) {
0145                     if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Cc)) {
0146                         qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
0147                         resultTooManyRecipients = true;
0148                         break;
0149                     }
0150                 }
0151             }
0152         }
0153         if (!resultTooManyRecipients) {
0154             if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
0155                 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
0156                 for (const QString &addr : spl) {
0157                     if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
0158                         qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
0159                         resultTooManyRecipients = true;
0160                         break;
0161                     }
0162                 }
0163             }
0164         }
0165         if (!resultTooManyRecipients) {
0166             if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
0167                 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
0168                 for (const QString &addr : spl) {
0169                     if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::ReplyTo)) {
0170                         qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
0171                         resultTooManyRecipients = true;
0172                         break;
0173                     }
0174                 }
0175             }
0176         }
0177         Q_EMIT tooManyRecipient(resultTooManyRecipients);
0178     }
0179     // First, we copy the message and then parse it to the object tree parser.
0180     // The otp gets the message text out of it, in textualContent(), and also decrypts
0181     // the message if necessary.
0182     auto msgContent = new KMime::Content;
0183     msgContent->setContent(m_msg->encodedContent());
0184     msgContent->parse();
0185     MimeTreeParser::SimpleObjectTreeSource emptySource;
0186     MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
0187     emptySource.setDecryptMessage(allowDecryption);
0188     otp.parseObjectTree(msgContent);
0189 
0190     // Load the attachments
0191     const auto attachmentsOfExtraContents{otp.nodeHelper()->attachmentsOfExtraContents()};
0192     for (const auto &att : attachmentsOfExtraContents) {
0193         addAttachmentPart(att);
0194     }
0195     const auto attachments{msgContent->attachments()};
0196     for (const auto &att : attachments) {
0197         addAttachmentPart(att);
0198     }
0199 
0200     int transportId = -1;
0201     if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
0202         transportId = hdr->asUnicodeString().toInt();
0203     }
0204 
0205     if (m_transport) {
0206         const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId);
0207         if (transport) {
0208             if (!m_transport->setCurrentTransport(transport->id())) {
0209                 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to find transport id" << transport->id();
0210             }
0211         }
0212     }
0213 
0214     // Set the HTML text and collect HTML images
0215     QString htmlContent = otp.htmlContent();
0216     if (htmlContent.isEmpty()) {
0217         m_editor->setPlainText(otp.plainTextContent());
0218     } else {
0219         // Bug 372085 <div id="name"> is replaced in qtextedit by <a id="name">... => break url
0220         htmlContent.replace(QRegularExpression(QStringLiteral("<div\\s*id=\".*\">")), QStringLiteral("<div>"));
0221         m_editor->setHtml(htmlContent);
0222         Q_EMIT enableHtml();
0223         collectImages(m_msg.data());
0224     }
0225 
0226     if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
0227         m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
0228     }
0229     delete msgContent;
0230 }
0231 
0232 void ComposerViewBase::updateTemplate(const KMime::Message::Ptr &msg)
0233 {
0234     // First, we copy the message and then parse it to the object tree parser.
0235     // The otp gets the message text out of it, in textualContent(), and also decrypts
0236     // the message if necessary.
0237     auto msgContent = new KMime::Content;
0238     msgContent->setContent(msg->encodedContent());
0239     msgContent->parse();
0240     MimeTreeParser::SimpleObjectTreeSource emptySource;
0241     MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
0242     otp.parseObjectTree(msgContent);
0243     // Set the HTML text and collect HTML images
0244     if (!otp.htmlContent().isEmpty()) {
0245         m_editor->setHtml(otp.htmlContent());
0246         Q_EMIT enableHtml();
0247         collectImages(msg.data());
0248     } else {
0249         m_editor->setPlainText(otp.plainTextContent());
0250     }
0251 
0252     if (auto hdr = msg->headerByType("X-KMail-CursorPos")) {
0253         m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toInt());
0254     }
0255     delete msgContent;
0256 }
0257 
0258 void ComposerViewBase::saveMailSettings()
0259 {
0260     const auto identity = currentIdentity();
0261     auto header = new KMime::Headers::Generic("X-KMail-Transport");
0262     header->fromUnicodeString(QString::number(m_transport->currentTransportId()), "utf-8");
0263     m_msg->setHeader(header);
0264 
0265     header = new KMime::Headers::Generic("X-KMail-Transport-Name");
0266     header->fromUnicodeString(m_transport->currentText(), "utf-8");
0267     m_msg->setHeader(header);
0268 
0269     header = new KMime::Headers::Generic("X-KMail-Fcc");
0270     header->fromUnicodeString(QString::number(m_fccCollection.id()), "utf-8");
0271     m_msg->setHeader(header);
0272 
0273     header = new KMime::Headers::Generic("X-KMail-Identity");
0274     header->fromUnicodeString(QString::number(identity.uoid()), "utf-8");
0275     m_msg->setHeader(header);
0276 
0277     header = new KMime::Headers::Generic("X-KMail-Identity-Name");
0278     header->fromUnicodeString(identity.identityName(), "utf-8");
0279     m_msg->setHeader(header);
0280 
0281     header = new KMime::Headers::Generic("X-KMail-Dictionary");
0282     header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
0283     m_msg->setHeader(header);
0284 
0285     // Save the quote prefix which is used for this message. Each message can have
0286     // a different quote prefix, for example depending on the original sender.
0287     if (m_editor->quotePrefixName().isEmpty()) {
0288         m_msg->removeHeader("X-KMail-QuotePrefix");
0289     } else {
0290         header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
0291         header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
0292         m_msg->setHeader(header);
0293     }
0294 
0295     if (m_editor->composerControler()->isFormattingUsed()) {
0296         qCDebug(MESSAGECOMPOSER_LOG) << "HTML mode";
0297         header = new KMime::Headers::Generic("X-KMail-Markup");
0298         header->fromUnicodeString(QStringLiteral("true"), "utf-8");
0299         m_msg->setHeader(header);
0300     } else {
0301         m_msg->removeHeader("X-KMail-Markup");
0302         qCDebug(MESSAGECOMPOSER_LOG) << "Plain text";
0303     }
0304 }
0305 
0306 void ComposerViewBase::clearFollowUp()
0307 {
0308     mFollowUpDate = QDate();
0309     mFollowUpCollection = Akonadi::Collection();
0310 }
0311 
0312 void ComposerViewBase::send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher)
0313 {
0314     mSendMethod = method;
0315     mSaveIn = saveIn;
0316 
0317     KCursorSaver saver(Qt::WaitCursor);
0318     const auto identity = currentIdentity();
0319 
0320     if (identity.attachVcard() && m_attachmentController->attachOwnVcard()) {
0321         const QString vcardFileName = identity.vCardFile();
0322         if (!vcardFileName.isEmpty()) {
0323             m_attachmentController->addAttachmentUrlSync(QUrl::fromLocalFile(vcardFileName));
0324         }
0325     }
0326     saveMailSettings();
0327 
0328     if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
0329         const QString keepBtnText =
0330             m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
0331         const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
0332         int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
0333                                                        i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
0334                                                             "<p>do you want to delete your markup?</p></qt>"),
0335                                                        i18nc("@title:window", "Sign/Encrypt Message?"),
0336                                                        KGuiItem(yesBtnText),
0337                                                        KGuiItem(keepBtnText));
0338         if (KMessageBox::Cancel == ret) {
0339             return;
0340         }
0341         if (KMessageBox::ButtonCode::SecondaryAction == ret) {
0342             m_encrypt = false;
0343             m_sign = false;
0344         } else {
0345             Q_EMIT disableHtml(NoConfirmationNeeded);
0346         }
0347     }
0348 
0349     if (m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone) {
0350         // we can't use the state of the mail itself, to remember the
0351         // signing and encryption state, so let's add a header instead
0352         DraftSignatureState(m_msg).setState(m_sign);
0353         DraftEncryptionState(m_msg).setState(m_encrypt);
0354         DraftCryptoMessageFormatState(m_msg).setState(m_cryptoMessageFormat);
0355     } else {
0356         removeDraftCryptoHeaders(m_msg);
0357     }
0358 
0359     if (mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) {
0360         if (!MessageComposer::Util::sendMailDispatcherIsOnline(m_parentWidget)) {
0361             qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to set sendmaildispatcher online. Please verify it";
0362             return;
0363         }
0364     }
0365 
0366     readyForSending();
0367 }
0368 
0369 void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
0370 {
0371     m_customHeader = customHeader;
0372 }
0373 
0374 void ComposerViewBase::readyForSending()
0375 {
0376     qCDebug(MESSAGECOMPOSER_LOG) << "Entering readyForSending";
0377     if (!m_msg) {
0378         qCDebug(MESSAGECOMPOSER_LOG) << "m_msg == 0!";
0379         return;
0380     }
0381 
0382     if (!m_composers.isEmpty()) {
0383         // This may happen if e.g. the autosave timer calls applyChanges.
0384         qCDebug(MESSAGECOMPOSER_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
0385         return;
0386     }
0387 
0388     // first, expand all addresses
0389     auto job = new MessageComposer::EmailAddressResolveJob(this);
0390     const auto identity = currentIdentity();
0391     if (!identity.isNull()) {
0392         job->setDefaultDomainName(identity.defaultDomainName());
0393     }
0394     job->setFrom(from());
0395     job->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
0396     job->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
0397     job->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
0398     job->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
0399 
0400     connect(job, &MessageComposer::EmailAddressResolveJob::result, this, &ComposerViewBase::slotEmailAddressResolved);
0401     job->start();
0402 }
0403 
0404 void ComposerViewBase::slotEmailAddressResolved(KJob *job)
0405 {
0406     if (job->error()) {
0407         qCWarning(MESSAGECOMPOSER_LOG) << "An error occurred while resolving the email addresses:" << job->errorString();
0408         // This error could be caused by a broken search infrastructure, so we ignore it for now
0409         // to not block sending emails completely.
0410     }
0411 
0412     bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled();
0413 
0414     const MessageComposer::EmailAddressResolveJob *resolveJob = qobject_cast<MessageComposer::EmailAddressResolveJob *>(job);
0415     if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
0416         mExpandedFrom = resolveJob->expandedFrom();
0417         mExpandedTo = resolveJob->expandedTo();
0418         mExpandedCc = resolveJob->expandedCc();
0419         mExpandedBcc = resolveJob->expandedBcc();
0420         mExpandedReplyTo = resolveJob->expandedReplyTo();
0421         if (autoresizeImage) {
0422             QStringList listEmails;
0423             listEmails << mExpandedFrom;
0424             listEmails << mExpandedTo;
0425             listEmails << mExpandedCc;
0426             listEmails << mExpandedBcc;
0427             listEmails << mExpandedReplyTo;
0428             MessageComposer::Utils resizeUtils;
0429             autoresizeImage = resizeUtils.filterRecipients(listEmails);
0430         }
0431     } else { // saved to draft, so keep the old values, not very nice.
0432         mExpandedFrom = from();
0433         const auto recipients{m_recipientsEditor->recipients()};
0434         for (const MessageComposer::Recipient::Ptr &r : recipients) {
0435             switch (r->type()) {
0436             case MessageComposer::Recipient::To:
0437                 mExpandedTo << r->email();
0438                 break;
0439             case MessageComposer::Recipient::Cc:
0440                 mExpandedCc << r->email();
0441                 break;
0442             case MessageComposer::Recipient::Bcc:
0443                 mExpandedBcc << r->email();
0444                 break;
0445             case MessageComposer::Recipient::ReplyTo:
0446                 mExpandedReplyTo << r->email();
0447                 break;
0448             case MessageComposer::Recipient::Undefined:
0449                 Q_ASSERT(!"Unknown recipient type!");
0450                 break;
0451             }
0452         }
0453         QStringList unExpandedTo;
0454         QStringList unExpandedCc;
0455         QStringList unExpandedBcc;
0456         QStringList unExpandedReplyTo;
0457         const auto expandedToLst{resolveJob->expandedTo()};
0458         for (const QString &exp : expandedToLst) {
0459             if (!mExpandedTo.contains(exp)) { // this address was expanded, so save it explicitly
0460                 unExpandedTo << exp;
0461             }
0462         }
0463         const auto expandedCcLst{resolveJob->expandedCc()};
0464         for (const QString &exp : expandedCcLst) {
0465             if (!mExpandedCc.contains(exp)) {
0466                 unExpandedCc << exp;
0467             }
0468         }
0469         const auto expandedBCcLst{resolveJob->expandedBcc()};
0470         for (const QString &exp : expandedBCcLst) {
0471             if (!mExpandedBcc.contains(exp)) { // this address was expanded, so save it explicitly
0472                 unExpandedBcc << exp;
0473             }
0474         }
0475         const auto expandedReplyLst{resolveJob->expandedReplyTo()};
0476         for (const QString &exp : expandedReplyLst) {
0477             if (!mExpandedReplyTo.contains(exp)) { // this address was expanded, so save it explicitly
0478                 unExpandedReplyTo << exp;
0479             }
0480         }
0481         auto header = new KMime::Headers::Generic("X-KMail-UnExpanded-To");
0482         header->from7BitString(unExpandedTo.join(QLatin1StringView(", ")).toLatin1());
0483         m_msg->setHeader(header);
0484         header = new KMime::Headers::Generic("X-KMail-UnExpanded-CC");
0485         header->from7BitString(unExpandedCc.join(QLatin1StringView(", ")).toLatin1());
0486         m_msg->setHeader(header);
0487         header = new KMime::Headers::Generic("X-KMail-UnExpanded-BCC");
0488         header->from7BitString(unExpandedBcc.join(QLatin1StringView(", ")).toLatin1());
0489         m_msg->setHeader(header);
0490         header = new KMime::Headers::Generic("X-KMail-UnExpanded-Reply-To");
0491         header->from7BitString(unExpandedReplyTo.join(QLatin1StringView(", ")).toLatin1());
0492         m_msg->setHeader(header);
0493         autoresizeImage = false;
0494     }
0495 
0496     Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
0497     // checks for emptiness before calling it
0498     // so just ensure it actually is empty
0499     // and document it
0500     // we first figure out if we need to create multiple messages with different crypto formats
0501     // if so, we create a composer per format
0502     // if we aren't signing or encrypting, this just returns a single empty message
0503     if (m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone && !mSendLaterInfo) {
0504         auto composer = new MessageComposer::Composer;
0505         composer->setNoCrypto(true);
0506         m_composers.append(composer);
0507     } else {
0508         bool wasCanceled = false;
0509         m_composers = generateCryptoMessages(wasCanceled);
0510         if (wasCanceled) {
0511             return;
0512         }
0513     }
0514 
0515     if (m_composers.isEmpty()) {
0516         Q_EMIT failed(i18n("It was not possible to create a message composer."));
0517         return;
0518     }
0519 
0520     if (autoresizeImage) {
0521         if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) {
0522             if (m_attachmentModel) {
0523                 MessageComposer::Utils resizeUtils;
0524                 if (resizeUtils.containsImage(m_attachmentModel->attachments())) {
0525                     const int rc = KMessageBox::warningTwoActions(m_parentWidget,
0526                                                                   i18n("Do you want to resize images?"),
0527                                                                   i18nc("@title:window", "Auto Resize Images"),
0528                                                                   KGuiItem(i18nc("@action:button", "Auto Resize")),
0529                                                                   KGuiItem(i18nc("@action:button", "Do Not Resize")));
0530                     if (rc == KMessageBox::ButtonCode::PrimaryAction) {
0531                         autoresizeImage = true;
0532                     } else {
0533                         autoresizeImage = false;
0534                     }
0535                 } else {
0536                     autoresizeImage = false;
0537                 }
0538             }
0539         }
0540     }
0541     // Compose each message and prepare it for queueing, sending, or storing
0542 
0543     // working copy in case composers instantly emit result
0544     const auto composers = m_composers;
0545     for (MessageComposer::Composer *composer : composers) {
0546         fillComposer(composer, UseExpandedRecipients, autoresizeImage);
0547         connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
0548         composer->start();
0549         qCDebug(MESSAGECOMPOSER_LOG) << "Started a composer for sending!";
0550     }
0551 }
0552 
0553 namespace
0554 {
0555 // helper methods for reading encryption settings
0556 
0557 inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
0558 {
0559     if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
0560         return Kleo::chrono::days{-1};
0561     }
0562     const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
0563     return Kleo::chrono::days{qMax(1, num)};
0564 }
0565 
0566 inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
0567 {
0568     if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
0569         return Kleo::chrono::days{-1};
0570     }
0571     const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
0572     return Kleo::chrono::days{qMax(1, num)};
0573 }
0574 
0575 inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
0576 {
0577     if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
0578         return Kleo::chrono::days{-1};
0579     }
0580     const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
0581     return Kleo::chrono::days{qMax(1, num)};
0582 }
0583 
0584 inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
0585 {
0586     if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
0587         return Kleo::chrono::days{-1};
0588     }
0589     const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
0590     return Kleo::chrono::days{qMax(1, num)};
0591 }
0592 
0593 inline bool showKeyApprovalDialog()
0594 {
0595     return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
0596 }
0597 
0598 inline bool cryptoWarningUnsigned(const KIdentityManagementCore::Identity &identity)
0599 {
0600     if (identity.encryptionOverride()) {
0601         return identity.warnNotSign();
0602     }
0603     return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned();
0604 }
0605 
0606 inline bool cryptoWarningUnencrypted(const KIdentityManagementCore::Identity &identity)
0607 {
0608     if (identity.encryptionOverride()) {
0609         return identity.warnNotEncrypt();
0610     }
0611     return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted();
0612 }
0613 } // nameless namespace
0614 
0615 bool ComposerViewBase::addKeysToContext(const QString &gnupgHome,
0616                                         const QList<QPair<QStringList, std::vector<GpgME::Key>>> &data,
0617                                         const std::map<QByteArray, QString> &autocryptMap)
0618 {
0619     bool needSpecialContext = false;
0620 
0621     for (const auto &p : data) {
0622         for (const auto &k : p.second) {
0623             const auto it = autocryptMap.find(k.primaryFingerprint());
0624             if (it != autocryptMap.end()) {
0625                 needSpecialContext = true;
0626                 break;
0627             }
0628         }
0629         if (needSpecialContext) {
0630             break;
0631         }
0632     }
0633 
0634     if (!needSpecialContext) {
0635         return false;
0636     }
0637     const QGpgME::Protocol *proto(QGpgME::openpgp());
0638 
0639     const auto storage = MessageCore::AutocryptStorage::self();
0640     QEventLoop loop;
0641     int runningJobs = 0;
0642     for (const auto &p : data) {
0643         for (const auto &k : p.second) {
0644             const auto it = autocryptMap.find(k.primaryFingerprint());
0645             if (it == autocryptMap.end()) {
0646                 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "via Export/Import";
0647                 auto exportJob = proto->publicKeyExportJob(false);
0648                 connect(exportJob,
0649                         &QGpgME::ExportJob::result,
0650                         [&gnupgHome, &proto, &runningJobs, &loop, &k](const GpgME::Error &result,
0651                                                                       const QByteArray &keyData,
0652                                                                       const QString &auditLogAsHtml,
0653                                                                       const GpgME::Error &auditLogError) {
0654                             Q_UNUSED(auditLogAsHtml);
0655                             Q_UNUSED(auditLogError);
0656                             if (result) {
0657                                 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to export " << k.primaryFingerprint() << result.asString();
0658                                 --runningJobs;
0659                                 if (runningJobs < 1) {
0660                                     loop.quit();
0661                                 }
0662                             }
0663 
0664                             auto importJob = proto->importJob();
0665                             QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
0666                             importJob->exec(keyData);
0667                             importJob->deleteLater();
0668                             --runningJobs;
0669                             if (runningJobs < 1) {
0670                                 loop.quit();
0671                             }
0672                         });
0673                 QStringList patterns;
0674                 patterns << QString::fromUtf8(k.primaryFingerprint());
0675                 runningJobs++;
0676                 exportJob->start(patterns);
0677                 exportJob->setExportFlags(GpgME::Context::ExportMinimal);
0678             } else {
0679                 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage";
0680                 const auto recipient = storage->getRecipient(it->second.toUtf8());
0681                 auto key = recipient->gpgKey();
0682                 auto keydata = recipient->gpgKeydata();
0683                 if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) {
0684                     qCDebug(MESSAGECOMPOSER_LOG) << "Using gossipkey";
0685                     keydata = recipient->gossipKeydata();
0686                 }
0687                 auto importJob = proto->importJob();
0688                 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
0689                 const auto result = importJob->exec(keydata);
0690                 importJob->deleteLater();
0691             }
0692         }
0693     }
0694     loop.exec();
0695     return true;
0696 }
0697 
0698 void ComposerViewBase::setAkonadiLookupEnabled(bool akonadiLookupEnabled)
0699 {
0700     m_akonadiLookupEnabled = akonadiLookupEnabled;
0701 }
0702 
0703 QList<MessageComposer::Composer *> ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
0704 {
0705     const auto id = currentIdentity();
0706 
0707     bool canceled = false;
0708 
0709     qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
0710     connect(expiryChecker().get(),
0711             &Kleo::ExpiryChecker::expiryMessage,
0712             this,
0713             [&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
0714                 if (!isNewMessage) {
0715                     return;
0716                 }
0717 
0718                 if (canceled) {
0719                     return;
0720                 }
0721                 QString title;
0722                 QString dontAskAgainName;
0723                 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
0724                     dontAskAgainName = QStringLiteral("own key expires soon warning");
0725                 } else {
0726                     dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
0727                 }
0728                 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
0729                     title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
0730                 } else {
0731                     title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
0732                 }
0733                 if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
0734                     == KMessageBox::Cancel) {
0735                     canceled = true;
0736                 }
0737             });
0738 
0739     QScopedPointer<Kleo::KeyResolver> keyResolver(
0740         new Kleo::KeyResolver(true, showKeyApprovalDialog(), id.pgpAutoEncrypt(), m_cryptoMessageFormat, expiryChecker()));
0741 
0742     keyResolver->setAutocryptEnabled(autocryptEnabled());
0743     keyResolver->setAkonadiLookupEnabled(m_akonadiLookupEnabled);
0744 
0745     QStringList encryptToSelfKeys;
0746     QStringList signKeys;
0747 
0748     bool signSomething = m_sign;
0749     bool doSignCompletely = m_sign;
0750     bool encryptSomething = m_encrypt;
0751     bool doEncryptCompletely = m_encrypt;
0752 
0753     // Add encryptionkeys from id to keyResolver
0754     if (!id.pgpEncryptionKey().isEmpty()) {
0755         encryptToSelfKeys.push_back(QLatin1StringView(id.pgpEncryptionKey()));
0756     }
0757     if (!id.smimeEncryptionKey().isEmpty()) {
0758         encryptToSelfKeys.push_back(QLatin1StringView(id.smimeEncryptionKey()));
0759     }
0760     if (canceled || keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
0761         qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
0762         return {};
0763     }
0764 
0765     // Add signingkeys from id to keyResolver
0766     if (!id.pgpSigningKey().isEmpty()) {
0767         signKeys.push_back(QLatin1StringView(id.pgpSigningKey()));
0768     }
0769     if (!id.smimeSigningKey().isEmpty()) {
0770         signKeys.push_back(QLatin1StringView(id.smimeSigningKey()));
0771     }
0772     if (canceled || keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
0773         qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
0774         return {};
0775     }
0776 
0777     if (m_attachmentModel) {
0778         const auto attachments = m_attachmentModel->attachments();
0779         for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
0780             if (attachment->isSigned()) {
0781                 signSomething = true;
0782             } else {
0783                 doEncryptCompletely = false;
0784             }
0785             if (attachment->isEncrypted()) {
0786                 encryptSomething = true;
0787             } else {
0788                 doSignCompletely = false;
0789             }
0790         }
0791     }
0792 
0793     const QStringList recipients = mExpandedTo + mExpandedCc;
0794     const QStringList bcc(mExpandedBcc);
0795 
0796     keyResolver->setPrimaryRecipients(recipients);
0797     keyResolver->setSecondaryRecipients(bcc);
0798 
0799     bool result = true;
0800     canceled = false;
0801     signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
0802     if (!result) {
0803         // TODO handle failure
0804         qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
0805         if (!canceled) {
0806             Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
0807         } else {
0808             Q_EMIT failed(QString());
0809         }
0810         wasCanceled = canceled;
0811         return {};
0812     }
0813 
0814     canceled = false;
0815     encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
0816     if (!result) {
0817         // TODO handle failure
0818         qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
0819         if (!canceled) {
0820             Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
0821         } else {
0822             Q_EMIT failed(QString());
0823         }
0824 
0825         wasCanceled = canceled;
0826         return {};
0827     }
0828 
0829     QList<MessageComposer::Composer *> composers;
0830 
0831     // No encryption or signing is needed
0832     if (!signSomething && !encryptSomething) {
0833         auto composer = new MessageComposer::Composer;
0834         if (m_cryptoMessageFormat & Kleo::OpenPGPMIMEFormat) {
0835             composer->setAutocryptEnabled(autocryptEnabled());
0836             if (keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat).size() > 0) {
0837                 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]);
0838             }
0839         }
0840         composers.append(composer);
0841         return composers;
0842     }
0843 
0844     canceled = false;
0845     const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
0846     if (kpgpResult == Kleo::Canceled || canceled) {
0847         qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
0848         return {};
0849     } else if (kpgpResult != Kleo::Ok) {
0850         // TODO handle failure
0851         qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
0852         Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
0853         return {};
0854     }
0855 
0856     qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys.";
0857 
0858     if (encryptSomething || signSomething) {
0859         Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
0860         for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
0861             concreteFormat = concreteCryptoMessageFormats[i];
0862             const auto encData = keyResolver->encryptionItems(concreteFormat);
0863             if (encData.empty()) {
0864                 continue;
0865             }
0866 
0867             if (!(concreteFormat & m_cryptoMessageFormat)) {
0868                 continue;
0869             }
0870 
0871             auto composer = new MessageComposer::Composer;
0872 
0873             if (encryptSomething || autocryptEnabled()) {
0874                 auto end(encData.end());
0875                 QList<QPair<QStringList, std::vector<GpgME::Key>>> data;
0876                 data.reserve(encData.size());
0877                 for (auto it = encData.begin(); it != end; ++it) {
0878                     QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
0879                     data.append(p);
0880                     qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
0881                 }
0882                 composer->setEncryptionKeys(data);
0883                 if (concreteFormat & Kleo::OpenPGPMIMEFormat && autocryptEnabled()) {
0884                     composer->setAutocryptEnabled(autocryptEnabled());
0885                     composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
0886                     QTemporaryDir dir;
0887                     bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
0888                     if (specialGnupgHome) {
0889                         dir.setAutoRemove(false);
0890                         composer->setGnupgHome(dir.path());
0891                     }
0892                 }
0893             }
0894 
0895             if (signSomething) {
0896                 // find signing keys for this format
0897                 std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
0898                 composer->setSigningKeys(signingKeys);
0899             }
0900 
0901             composer->setMessageCryptoFormat(concreteFormat);
0902             composer->setSignAndEncrypt(signSomething, encryptSomething);
0903 
0904             composers.append(composer);
0905         }
0906     } else {
0907         auto composer = new MessageComposer::Composer;
0908         composers.append(composer);
0909         // If we canceled sign or encrypt be sure to change status in attachment.
0910         markAllAttachmentsForSigning(false);
0911         markAllAttachmentsForEncryption(false);
0912     }
0913 
0914     if (composers.isEmpty() && (signSomething || encryptSomething)) {
0915         Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
0916     }
0917 
0918     return composers;
0919 }
0920 
0921 void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
0922 {
0923     globalPart->setParentWidgetForGui(m_parentWidget);
0924     globalPart->setCharsets(m_charsets);
0925     globalPart->setMDNRequested(m_mdnRequested);
0926     globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
0927 }
0928 
0929 void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
0930 {
0931     // TODO splitAddressList and expandAliases ugliness should be handled by a
0932     // special AddressListEdit widget... (later: see RecipientsEditor)
0933 
0934     if (m_fccCombo) {
0935         infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
0936     } else {
0937         if (m_fccCollection.isValid()) {
0938             infoPart->setFcc(QString::number(m_fccCollection.id()));
0939         }
0940     }
0941 
0942     infoPart->setTransportId(m_transport->currentTransportId());
0943     if (expansion == UseExpandedRecipients) {
0944         infoPart->setFrom(mExpandedFrom);
0945         infoPart->setTo(mExpandedTo);
0946         infoPart->setCc(mExpandedCc);
0947         infoPart->setBcc(mExpandedBcc);
0948         infoPart->setReplyTo(mExpandedReplyTo);
0949     } else {
0950         infoPart->setFrom(from());
0951         infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
0952         infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
0953         infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
0954         infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
0955     }
0956     infoPart->setSubject(subject());
0957     infoPart->setUserAgent(QStringLiteral("KMail"));
0958     infoPart->setUrgent(m_urgent);
0959 
0960     if (auto inReplyTo = m_msg->inReplyTo(false)) {
0961         infoPart->setInReplyTo(inReplyTo->asUnicodeString());
0962     }
0963 
0964     if (auto references = m_msg->references(false)) {
0965         infoPart->setReferences(references->asUnicodeString());
0966     }
0967 
0968     KMime::Headers::Base::List extras;
0969     if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
0970         extras << hdr;
0971     }
0972     if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
0973         extras << hdr;
0974     }
0975     if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
0976         extras << hdr;
0977     }
0978     if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
0979         extras << hdr;
0980     }
0981     if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
0982         extras << hdr;
0983     }
0984     if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
0985         extras << hdr;
0986     }
0987     if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
0988         extras << hdr;
0989     }
0990     if (auto hdr = m_msg->organization(false)) {
0991         extras << hdr;
0992     }
0993     if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
0994         extras << hdr;
0995     }
0996     if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
0997         extras << hdr;
0998     }
0999     if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
1000         extras << hdr;
1001     }
1002     if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
1003         extras << hdr;
1004     }
1005     if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
1006         extras << hdr;
1007     }
1008     if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
1009         extras << hdr;
1010     }
1011     if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
1012         extras << hdr;
1013     }
1014     if (auto hdr = m_msg->headerByType("X-Face")) {
1015         extras << hdr;
1016     }
1017     if (auto hdr = m_msg->headerByType("Face")) {
1018         extras << hdr;
1019     }
1020     if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
1021         extras << hdr;
1022     }
1023     if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
1024         extras << hdr;
1025     }
1026     if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1027         extras << hdr;
1028     }
1029 
1030     infoPart->setExtraHeaders(extras);
1031 }
1032 
1033 void ComposerViewBase::slotSendComposeResult(KJob *job)
1034 {
1035     Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1036     auto composer = static_cast<MessageComposer::Composer *>(job);
1037     if (composer->error() != MessageComposer::Composer::NoError) {
1038         qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1039     }
1040 
1041     if (composer->error() == MessageComposer::Composer::NoError) {
1042         Q_ASSERT(m_composers.contains(composer));
1043         // The messages were composed successfully.
1044         qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1045         const int numberOfMessage(composer->resultMessages().size());
1046         for (int i = 0; i < numberOfMessage; ++i) {
1047             if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1048                 queueMessage(composer->resultMessages().at(i), composer);
1049             } else {
1050                 saveMessage(composer->resultMessages().at(i), mSaveIn);
1051             }
1052         }
1053         saveRecentAddresses(composer->resultMessages().at(0));
1054     } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1055         // The job warned the user about something, and the user chose to return
1056         // to the message.  Nothing to do.
1057         qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1058         Q_EMIT failed(i18n("Job cancelled by the user"));
1059     } else {
1060         qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1061         QString msg;
1062         if (composer->error() == MessageComposer::Composer::BugError) {
1063             msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1064         } else {
1065             msg = i18n("Could not compose message: %1", job->errorString());
1066         }
1067         Q_EMIT failed(msg);
1068     }
1069 
1070     if (!composer->gnupgHome().isEmpty()) {
1071         QDir dir(composer->gnupgHome());
1072         dir.removeRecursively();
1073     }
1074 
1075     m_composers.removeAll(composer);
1076 }
1077 
1078 void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1079 {
1080     KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1081     const QList<QByteArray> toAddresses = msg->to()->addresses();
1082     for (const QByteArray &address : toAddresses) {
1083         PimCommon::RecentAddresses::self(config)->add(QLatin1StringView(address));
1084     }
1085     const QList<QByteArray> ccAddresses = msg->cc()->addresses();
1086     for (const QByteArray &address : ccAddresses) {
1087         PimCommon::RecentAddresses::self(config)->add(QLatin1StringView(address));
1088     }
1089     const QList<QByteArray> bccAddresses = msg->bcc()->addresses();
1090     for (const QByteArray &address : bccAddresses) {
1091         PimCommon::RecentAddresses::self(config)->add(QLatin1StringView(address));
1092     }
1093 }
1094 
1095 void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
1096 {
1097     const MessageComposer::InfoPart *infoPart = composer->infoPart();
1098     auto qjob = new Akonadi::MessageQueueJob(this);
1099     qjob->setMessage(message);
1100     qjob->transportAttribute().setTransportId(infoPart->transportId());
1101     if (mSendMethod == MessageComposer::MessageSender::SendLater) {
1102         qjob->dispatchModeAttribute().setDispatchMode(Akonadi::DispatchModeAttribute::Manual);
1103     }
1104 
1105     if (message->hasHeader("X-KMail-FccDisabled")) {
1106         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete);
1107     } else if (!infoPart->fcc().isEmpty()) {
1108         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
1109 
1110         const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
1111         qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
1112     } else {
1113         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToDefaultSentCollection);
1114     }
1115 
1116     MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
1117     if (transport && transport->specifySenderOverwriteAddress()) {
1118         qjob->addressAttribute().setFrom(
1119             KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(transport->senderOverwriteAddress())));
1120     } else {
1121         qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
1122     }
1123     // if this header is not empty, it contains the real recipient of the message, either the primary or one of the
1124     //  secondary recipients. so we set that to the transport job, while leaving the message itself alone.
1125     if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
1126         qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(realTo->asUnicodeString().split(QLatin1Char('%'))));
1127         message->removeHeader("X-KMail-EncBccRecipients");
1128         message->assemble();
1129         qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
1130     } else {
1131         qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->to()));
1132         qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->cc()));
1133         qjob->addressAttribute().setBcc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->bcc()));
1134     }
1135     if (m_requestDeleveryConfirmation) {
1136         qjob->addressAttribute().setDeliveryStatusNotification(true);
1137     }
1138     MessageComposer::Util::addSendReplyForwardAction(message, qjob);
1139     MessageCore::StringUtil::removePrivateHeaderFields(message, false);
1140 
1141     MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1142     message->assemble();
1143     connect(qjob, &Akonadi::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
1144     m_pendingQueueJobs++;
1145     qjob->start();
1146 
1147     qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
1148 }
1149 
1150 void ComposerViewBase::slotQueueResult(KJob *job)
1151 {
1152     m_pendingQueueJobs--;
1153     auto qjob = static_cast<Akonadi::MessageQueueJob *>(job);
1154     qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
1155     Q_ASSERT(m_pendingQueueJobs >= 0);
1156 
1157     if (job->error()) {
1158         qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
1159         // There is not much we can do now, since all the MessageQueueJobs have been
1160         // started.  So just wait for them to finish.
1161         // TODO show a message box or something
1162         QString msg = i18n("There were problems trying to queue the message for sending: %1", job->errorString());
1163 
1164         if (m_pendingQueueJobs == 0) {
1165             Q_EMIT failed(msg);
1166             return;
1167         }
1168     }
1169 
1170     if (m_pendingQueueJobs == 0) {
1171         addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
1172         Q_EMIT sentSuccessfully(-1);
1173     }
1174 }
1175 
1176 void ComposerViewBase::initAutoSave()
1177 {
1178     qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
1179 
1180     // Ensure that the autosave directory exists.
1181     QDir dataDirectory(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1StringView("/kmail2/"));
1182     if (!dataDirectory.exists(QStringLiteral("autosave"))) {
1183         qCDebug(MESSAGECOMPOSER_LOG) << "Creating autosave directory.";
1184         dataDirectory.mkdir(QStringLiteral("autosave"));
1185     }
1186 
1187     // Construct a file name
1188     if (m_autoSaveUUID.isEmpty()) {
1189         m_autoSaveUUID = QUuid::createUuid().toString();
1190     }
1191 
1192     updateAutoSave();
1193 }
1194 
1195 Akonadi::Collection ComposerViewBase::followUpCollection() const
1196 {
1197     return mFollowUpCollection;
1198 }
1199 
1200 void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1201 {
1202     mFollowUpCollection = followUpCollection;
1203 }
1204 
1205 QDate ComposerViewBase::followUpDate() const
1206 {
1207     return mFollowUpDate;
1208 }
1209 
1210 void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1211 {
1212     mFollowUpDate = followUpDate;
1213 }
1214 
1215 Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1216 {
1217     return m_dictionary;
1218 }
1219 
1220 void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1221 {
1222     m_dictionary = dictionary;
1223 }
1224 
1225 void ComposerViewBase::updateAutoSave()
1226 {
1227     if (m_autoSaveInterval == 0) {
1228         delete m_autoSaveTimer;
1229         m_autoSaveTimer = nullptr;
1230     } else {
1231         if (!m_autoSaveTimer) {
1232             m_autoSaveTimer = new QTimer(this);
1233             if (m_parentWidget) {
1234                 connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
1235             } else {
1236                 connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
1237             }
1238         }
1239         m_autoSaveTimer->start(m_autoSaveInterval);
1240     }
1241 }
1242 
1243 void ComposerViewBase::cleanupAutoSave()
1244 {
1245     delete m_autoSaveTimer;
1246     m_autoSaveTimer = nullptr;
1247     if (!m_autoSaveUUID.isEmpty()) {
1248         qCDebug(MESSAGECOMPOSER_LOG) << "deleting autosave files" << m_autoSaveUUID;
1249 
1250         // Delete the autosave files
1251         QDir autoSaveDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1StringView("/kmail2/autosave"));
1252 
1253         // Filter out only this composer window's autosave files
1254         const QStringList autoSaveFilter{m_autoSaveUUID + QLatin1StringView("*")};
1255         autoSaveDir.setNameFilters(autoSaveFilter);
1256 
1257         // Return the files to be removed
1258         const QStringList autoSaveFiles = autoSaveDir.entryList();
1259         qCDebug(MESSAGECOMPOSER_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
1260 
1261         // Delete each file
1262         for (const QString &file : autoSaveFiles) {
1263             autoSaveDir.remove(file);
1264         }
1265         m_autoSaveUUID.clear();
1266     }
1267 }
1268 
1269 //-----------------------------------------------------------------------------
1270 void ComposerViewBase::autoSaveMessage()
1271 {
1272     qCDebug(MESSAGECOMPOSER_LOG) << "Autosaving message";
1273 
1274     if (m_autoSaveTimer) {
1275         m_autoSaveTimer->stop();
1276     }
1277 
1278     if (!m_composers.isEmpty()) {
1279         // This may happen if e.g. the autosave timer calls applyChanges.
1280         qCDebug(MESSAGECOMPOSER_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
1281         return;
1282     }
1283 
1284     auto composer = new Composer();
1285     fillComposer(composer);
1286     composer->setAutoSave(true);
1287     composer->setAutocryptEnabled(autocryptEnabled());
1288     m_composers.append(composer);
1289     connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
1290     composer->start();
1291 }
1292 
1293 void ComposerViewBase::setAutoSaveFileName(const QString &fileName)
1294 {
1295     m_autoSaveUUID = fileName;
1296 
1297     Q_EMIT modified(true);
1298 }
1299 
1300 void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
1301 {
1302     using MessageComposer::Composer;
1303 
1304     Q_ASSERT(dynamic_cast<Composer *>(job));
1305     auto composer = static_cast<Composer *>(job);
1306 
1307     if (composer->error() == Composer::NoError) {
1308         Q_ASSERT(m_composers.contains(composer));
1309 
1310         // The messages were composed successfully. Only save the first message, there should
1311         // only be one anyway, since crypto is disabled.
1312         qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1313         writeAutoSaveToDisk(composer->resultMessages().constFirst());
1314         Q_ASSERT(composer->resultMessages().size() == 1);
1315 
1316         if (m_autoSaveInterval > 0) {
1317             updateAutoSave();
1318         }
1319     } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1320         // The job warned the user about something, and the user chose to return
1321         // to the message.  Nothing to do.
1322         qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1323         Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
1324     } else {
1325         qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
1326         Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
1327     }
1328 
1329     m_composers.removeAll(composer);
1330 }
1331 
1332 void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1333 {
1334     const QString autosavePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1StringView("/kmail2/autosave/");
1335     QDir().mkpath(autosavePath);
1336     const QString filename = autosavePath + m_autoSaveUUID;
1337     QSaveFile file(filename);
1338     QString errorMessage;
1339     qCDebug(MESSAGECOMPOSER_LOG) << "Writing message to disk as" << filename;
1340 
1341     if (file.open(QIODevice::WriteOnly)) {
1342         file.setPermissions(QFile::ReadUser | QFile::WriteUser);
1343 
1344         if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
1345             errorMessage = i18n("Could not write all data to file.");
1346         } else {
1347             if (!file.commit()) {
1348                 errorMessage = i18n("Could not finalize the file.");
1349             }
1350         }
1351     } else {
1352         errorMessage = i18n("Could not open file.");
1353     }
1354 
1355     if (!errorMessage.isEmpty()) {
1356         qCWarning(MESSAGECOMPOSER_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
1357         if (!m_autoSaveErrorShown) {
1358             KMessageBox::error(m_parentWidget,
1359                                i18n("Autosaving the message as %1 failed.\n"
1360                                     "%2\n"
1361                                     "Reason: %3",
1362                                     filename,
1363                                     errorMessage,
1364                                     file.errorString()),
1365                                i18nc("@title:window", "Autosaving Message Failed"));
1366 
1367             // Error dialog shown, hide the errors the next time
1368             m_autoSaveErrorShown = true;
1369         }
1370     } else {
1371         // No error occurred, the next error should be shown again
1372         m_autoSaveErrorShown = false;
1373     }
1374     file.commit();
1375     message->clear();
1376 }
1377 
1378 void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
1379 {
1380     Akonadi::Collection target;
1381     const auto identity = currentIdentity();
1382     message->date()->setDateTime(QDateTime::currentDateTime());
1383     if (!identity.isNull()) {
1384         if (auto header = message->headerByType("X-KMail-Fcc")) {
1385             const int sentCollectionId = header->asUnicodeString().toInt();
1386             if (identity.fcc() == QString::number(sentCollectionId)) {
1387                 message->removeHeader("X-KMail-Fcc");
1388             }
1389         }
1390     }
1391     MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1392 
1393     message->assemble();
1394 
1395     Akonadi::Item item;
1396     item.setMimeType(QStringLiteral("message/rfc822"));
1397     item.setPayload(message);
1398     Akonadi::MessageFlags::copyMessageFlags(*message, item);
1399 
1400     if (!identity.isNull()) { // we have a valid identity
1401         switch (saveIn) {
1402         case MessageComposer::MessageSender::SaveInTemplates:
1403             if (!identity.templates().isEmpty()) { // the user has specified a custom templates collection
1404                 target = Akonadi::Collection(identity.templates().toLongLong());
1405             }
1406             break;
1407         case MessageComposer::MessageSender::SaveInDrafts:
1408             if (!identity.drafts().isEmpty()) { // the user has specified a custom drafts collection
1409                 target = Akonadi::Collection(identity.drafts().toLongLong());
1410             }
1411             break;
1412         case MessageComposer::MessageSender::SaveInOutbox: // We don't define save outbox in identity
1413             target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Outbox);
1414             break;
1415         case MessageComposer::MessageSender::SaveInNone:
1416             break;
1417         }
1418 
1419         auto saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
1420         saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
1421         QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
1422     } else {
1423         // preinitialize with the default collections
1424         target = defaultSpecialTarget();
1425         auto create = new Akonadi::ItemCreateJob(item, target, this);
1426         connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1427         ++m_pendingQueueJobs;
1428     }
1429 }
1430 
1431 void ComposerViewBase::slotSaveMessage(KJob *job)
1432 {
1433     Akonadi::Collection target;
1434     auto item = job->property("Akonadi::Item").value<Akonadi::Item>();
1435     if (job->error()) {
1436         target = defaultSpecialTarget();
1437     } else {
1438         const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
1439         if (fetchJob->collections().isEmpty()) {
1440             target = defaultSpecialTarget();
1441         } else {
1442             target = fetchJob->collections().at(0);
1443         }
1444     }
1445     auto create = new Akonadi::ItemCreateJob(item, target, this);
1446     connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1447     ++m_pendingQueueJobs;
1448 }
1449 
1450 Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1451 {
1452     Akonadi::Collection target;
1453     switch (mSaveIn) {
1454     case MessageComposer::MessageSender::SaveInNone:
1455         break;
1456     case MessageComposer::MessageSender::SaveInDrafts:
1457         target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Drafts);
1458         break;
1459     case MessageComposer::MessageSender::SaveInTemplates:
1460         target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Templates);
1461         break;
1462     case MessageComposer::MessageSender::SaveInOutbox:
1463         target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Outbox);
1464         break;
1465     }
1466 
1467     return target;
1468 }
1469 
1470 void ComposerViewBase::slotCreateItemResult(KJob *job)
1471 {
1472     --m_pendingQueueJobs;
1473     qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
1474     Q_ASSERT(m_pendingQueueJobs >= 0);
1475 
1476     if (job->error()) {
1477         qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
1478         Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
1479         return;
1480     }
1481 
1482     Akonadi::Item::Id id = -1;
1483     if (mSendLaterInfo) {
1484         auto createJob = static_cast<Akonadi::ItemCreateJob *>(job);
1485         const Akonadi::Item item = createJob->item();
1486         if (item.isValid()) {
1487             id = item.id();
1488             addSendLaterItem(item);
1489         }
1490     }
1491 
1492     if (m_pendingQueueJobs == 0) {
1493         Q_EMIT sentSuccessfully(id);
1494     }
1495 }
1496 
1497 void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
1498 {
1499     Q_UNUSED(comment)
1500     qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
1501     if (sync) {
1502         m_attachmentController->addAttachmentUrlSync(url);
1503     } else {
1504         m_attachmentController->addAttachment(url);
1505     }
1506 }
1507 
1508 void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1509 {
1510     MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
1511     if (!data.isEmpty()) {
1512         attachment->setName(name);
1513         attachment->setFileName(filename);
1514         attachment->setData(data);
1515         attachment->setCharset(charset.toLatin1());
1516         attachment->setMimeType(mimeType);
1517         // TODO what about the other fields?
1518 
1519         m_attachmentController->addAttachment(attachment);
1520     }
1521 }
1522 
1523 void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1524 {
1525     MessageCore::AttachmentPart::Ptr part(new MessageCore::AttachmentPart);
1526     if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
1527         // if it is a digest or a full message, use the encodedContent() of the attachment,
1528         // which already has the proper headers
1529         part->setData(partToAttach->encodedContent());
1530     } else {
1531         part->setData(partToAttach->decodedContent());
1532     }
1533     part->setMimeType(partToAttach->contentType(false)->mimeType());
1534     if (auto cd = partToAttach->contentDescription(false)) {
1535         part->setDescription(cd->asUnicodeString());
1536     }
1537     if (auto ct = partToAttach->contentType(false)) {
1538         if (ct->hasParameter(QStringLiteral("name"))) {
1539             part->setName(ct->parameter(QStringLiteral("name")));
1540         }
1541     }
1542     if (auto cd = partToAttach->contentDisposition(false)) {
1543         part->setFileName(cd->filename());
1544         part->setInline(cd->disposition() == KMime::Headers::CDinline);
1545     }
1546     if (part->name().isEmpty() && !part->fileName().isEmpty()) {
1547         part->setName(part->fileName());
1548     }
1549     if (part->fileName().isEmpty() && !part->name().isEmpty()) {
1550         part->setFileName(part->name());
1551     }
1552     m_attachmentController->addAttachment(part);
1553 }
1554 
1555 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1556 {
1557     fillComposer(composer, UseUnExpandedRecipients, false);
1558 }
1559 
1560 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
1561 {
1562     fillGlobalPart(composer->globalPart());
1563     m_editor->fillComposerTextPart(composer->textPart());
1564     fillInfoPart(composer->infoPart(), expansion);
1565     if (m_attachmentModel) {
1566         composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
1567     }
1568 }
1569 
1570 //-----------------------------------------------------------------------------
1571 QString ComposerViewBase::to() const
1572 {
1573     if (m_recipientsEditor) {
1574         return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1575     }
1576     return {};
1577 }
1578 
1579 //-----------------------------------------------------------------------------
1580 QString ComposerViewBase::cc() const
1581 {
1582     if (m_recipientsEditor) {
1583         return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
1584     }
1585     return {};
1586 }
1587 
1588 //-----------------------------------------------------------------------------
1589 QString ComposerViewBase::bcc() const
1590 {
1591     if (m_recipientsEditor) {
1592         return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1593     }
1594     return {};
1595 }
1596 
1597 QString ComposerViewBase::from() const
1598 {
1599     return MessageComposer::Util::cleanedUpHeaderString(m_from);
1600 }
1601 
1602 QString ComposerViewBase::replyTo() const
1603 {
1604     if (m_recipientsEditor) {
1605         return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1606     }
1607     return {};
1608 }
1609 
1610 QString ComposerViewBase::subject() const
1611 {
1612     return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1613 }
1614 
1615 const KIdentityManagementCore::Identity &ComposerViewBase::currentIdentity() const
1616 {
1617     return m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1618 }
1619 
1620 bool ComposerViewBase::autocryptEnabled() const
1621 {
1622     return currentIdentity().autocryptEnabled();
1623 }
1624 
1625 void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1626 {
1627     m_parentWidget = w;
1628 }
1629 
1630 void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1631 {
1632     m_attachmentController = controller;
1633 }
1634 
1635 MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1636 {
1637     return m_attachmentController;
1638 }
1639 
1640 void ComposerViewBase::setAttachmentModel(MessageComposer::AttachmentModel *model)
1641 {
1642     m_attachmentModel = model;
1643 }
1644 
1645 MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1646 {
1647     return m_attachmentModel;
1648 }
1649 
1650 void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1651 {
1652     m_recipientsEditor = recEditor;
1653 }
1654 
1655 MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1656 {
1657     return m_recipientsEditor;
1658 }
1659 
1660 void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1661 {
1662     m_signatureController = sigController;
1663 }
1664 
1665 MessageComposer::SignatureController *ComposerViewBase::signatureController()
1666 {
1667     return m_signatureController;
1668 }
1669 
1670 void ComposerViewBase::setIdentityCombo(KIdentityManagementWidgets::IdentityCombo *identCombo)
1671 {
1672     m_identityCombo = identCombo;
1673 }
1674 
1675 KIdentityManagementWidgets::IdentityCombo *ComposerViewBase::identityCombo()
1676 {
1677     return m_identityCombo;
1678 }
1679 
1680 void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident,
1681                                         const KIdentityManagementCore::Identity &oldIdent,
1682                                         MessageComposer::Recipient::Type type)
1683 {
1684     QString oldIdentList;
1685     QString newIdentList;
1686     if (type == MessageComposer::Recipient::Bcc) {
1687         oldIdentList = oldIdent.bcc();
1688         newIdentList = ident.bcc();
1689     } else if (type == MessageComposer::Recipient::Cc) {
1690         oldIdentList = oldIdent.cc();
1691         newIdentList = ident.cc();
1692     } else if (type == MessageComposer::Recipient::ReplyTo) {
1693         oldIdentList = oldIdent.replyToAddr();
1694         newIdentList = ident.replyToAddr();
1695     } else {
1696         return;
1697     }
1698 
1699     if (oldIdentList != newIdentList) {
1700         const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
1701         for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1702             m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1703         }
1704 
1705         const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
1706         for (const KMime::Types::Mailbox &recipient : newRecipients) {
1707             m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1708         }
1709         m_recipientsEditor->setFocusBottom();
1710     }
1711 }
1712 
1713 void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
1714 {
1715     updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1716     updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1717     updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1718 
1719     KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
1720     KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
1721     // replace existing signatures
1722     const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1723     // Just append the signature if there was no old signature
1724     if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1725         signatureController()->applySignature(newSig);
1726     }
1727     const QString vcardFileName = ident.vCardFile();
1728     attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1729     attachmentController()->setAttachOwnVcard(ident.attachVcard());
1730 
1731     m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1732 }
1733 
1734 void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1735 {
1736     m_editor = editor;
1737     m_editor->document()->setModified(false);
1738 }
1739 
1740 MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1741 {
1742     return m_editor;
1743 }
1744 
1745 void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1746 {
1747     m_transport = transpCombo;
1748 }
1749 
1750 MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1751 {
1752     return m_transport;
1753 }
1754 
1755 void ComposerViewBase::setIdentityManager(KIdentityManagementCore::IdentityManager *identMan)
1756 {
1757     m_identMan = identMan;
1758 }
1759 
1760 KIdentityManagementCore::IdentityManager *ComposerViewBase::identityManager()
1761 {
1762     return m_identMan;
1763 }
1764 
1765 void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1766 {
1767     if (m_fccCombo) {
1768         m_fccCombo->setDefaultCollection(fccCollection);
1769     } else {
1770         m_fccCollection = fccCollection;
1771     }
1772     auto const checkFccCollectionJob = new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
1773     connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1774 }
1775 
1776 void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1777 {
1778     if (job->error()) {
1779         qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1780         const Akonadi::Collection sentMailCol = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::SentMail);
1781         if (m_fccCombo) {
1782             m_fccCombo->setDefaultCollection(sentMailCol);
1783         } else {
1784             m_fccCollection = sentMailCol;
1785         }
1786     }
1787 }
1788 
1789 void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1790 {
1791     m_fccCombo = fcc;
1792 }
1793 
1794 Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1795 {
1796     return m_fccCombo;
1797 }
1798 
1799 void ComposerViewBase::setFrom(const QString &from)
1800 {
1801     m_from = from;
1802 }
1803 
1804 void ComposerViewBase::setSubject(const QString &subject)
1805 {
1806     m_subject = subject;
1807     if (mSendLaterInfo) {
1808         mSendLaterInfo->setSubject(m_subject);
1809         mSendLaterInfo->setTo(to());
1810     }
1811 }
1812 
1813 void ComposerViewBase::setAutoSaveInterval(int interval)
1814 {
1815     m_autoSaveInterval = interval;
1816 }
1817 
1818 void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1819 {
1820     m_sign = sign;
1821     m_encrypt = encrypt;
1822     m_cryptoMessageFormat = format;
1823     m_neverEncrypt = neverEncryptDrafts;
1824 }
1825 
1826 void ComposerViewBase::setCharsets(const QList<QByteArray> &charsets)
1827 {
1828     m_charsets = charsets;
1829 }
1830 
1831 void ComposerViewBase::setMDNRequested(bool mdnRequested)
1832 {
1833     m_mdnRequested = mdnRequested;
1834 }
1835 
1836 void ComposerViewBase::setUrgent(bool urgent)
1837 {
1838     m_urgent = urgent;
1839 }
1840 
1841 int ComposerViewBase::autoSaveInterval() const
1842 {
1843     return m_autoSaveInterval;
1844 }
1845 
1846 //-----------------------------------------------------------------------------
1847 void ComposerViewBase::collectImages(KMime::Content *root)
1848 {
1849     if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1850         KMime::Content *parentnode = n->parent();
1851         if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1852             KMime::Content *node = MessageCore::NodeHelper::nextSibling(n);
1853             while (node) {
1854                 if (node->contentType()->isImage()) {
1855                     qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1856                     QImage img;
1857                     img.loadFromData(node->decodedContent());
1858                     m_editor->composerControler()->composerImages()->loadImage(
1859                         img,
1860                         QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1861                         node->contentType()->name());
1862                 }
1863                 node = MessageCore::NodeHelper::nextSibling(node);
1864             }
1865         }
1866     }
1867 }
1868 
1869 //-----------------------------------------------------------------------------
1870 bool ComposerViewBase::inlineSigningEncryptionSelected() const
1871 {
1872     if (!m_sign && !m_encrypt) {
1873         return false;
1874     }
1875     return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1876 }
1877 
1878 bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1879 {
1880     if (attachmentKeywords.isEmpty()) {
1881         return false;
1882     }
1883     if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1884         return false;
1885     }
1886 
1887     return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1888 }
1889 
1890 ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1891 {
1892     if (!hasMissingAttachments(attachmentKeywords)) {
1893         return NoMissingAttachmentFound;
1894     }
1895     const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
1896 
1897                                                         i18n("The message you have composed seems to refer to an "
1898                                                              "attached file but you have not attached anything.\n"
1899                                                              "Do you want to attach a file to your message?"),
1900                                                         i18nc("@title:window", "File Attachment Reminder"),
1901                                                         KGuiItem(i18n("&Attach File..."), QLatin1StringView("mail-attachment")),
1902                                                         KGuiItem(i18n("&Send as Is"), QLatin1StringView("mail-send")));
1903     if (rc == KMessageBox::Cancel) {
1904         return FoundMissingAttachmentAndCancel;
1905     }
1906     if (rc == KMessageBox::ButtonCode::PrimaryAction) {
1907         m_attachmentController->showAddAttachmentFileDialog();
1908         return FoundMissingAttachmentAndAddedAttachment;
1909     }
1910 
1911     return FoundMissingAttachmentAndSending;
1912 }
1913 
1914 void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1915 {
1916     if (m_attachmentModel) {
1917         const auto attachments = m_attachmentModel->attachments();
1918         for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1919             attachment->setSigned(sign);
1920         }
1921     }
1922 }
1923 
1924 void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1925 {
1926     if (m_attachmentModel) {
1927         const auto attachments = m_attachmentModel->attachments();
1928         for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1929             attachment->setEncrypted(encrypt);
1930         }
1931     }
1932 }
1933 
1934 bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1935 {
1936     bool sign = false;
1937     switch (keyResolver->checkSigningPreferences(signSomething)) {
1938     case Kleo::DoIt:
1939         if (!signSomething) {
1940             markAllAttachmentsForSigning(true);
1941             return true;
1942         }
1943         sign = true;
1944         break;
1945     case Kleo::DontDoIt:
1946         sign = false;
1947         break;
1948     case Kleo::AskOpportunistic:
1949         assert(0);
1950     case Kleo::Ask: {
1951         // the user wants to be asked or has to be asked
1952         KCursorSaver saver(Qt::WaitCursor);
1953         const QString msg = i18n(
1954             "Examination of the recipient's signing preferences "
1955             "yielded that you be asked whether or not to sign "
1956             "this message.\n"
1957             "Sign this message?");
1958         switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1959                                                      msg,
1960                                                      i18nc("@title:window", "Sign Message?"),
1961                                                      KGuiItem(i18nc("to sign", "&Sign")),
1962                                                      KGuiItem(i18n("Do &Not Sign")))) {
1963         case KMessageBox::Cancel:
1964             result = false;
1965             canceled = true;
1966             return false;
1967         case KMessageBox::ButtonCode::PrimaryAction:
1968             markAllAttachmentsForSigning(true);
1969             return true;
1970         case KMessageBox::ButtonCode::SecondaryAction:
1971             markAllAttachmentsForSigning(false);
1972             return false;
1973         default:
1974             qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1975             return false;
1976         }
1977         break;
1978     }
1979     case Kleo::Conflict: {
1980         // warn the user that there are conflicting signing preferences
1981         KCursorSaver saver(Qt::WaitCursor);
1982         const QString msg = i18n(
1983             "There are conflicting signing preferences "
1984             "for these recipients.\n"
1985             "Sign this message?");
1986         switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1987                                                      msg,
1988                                                      i18nc("@title:window", "Sign Message?"),
1989                                                      KGuiItem(i18nc("to sign", "&Sign")),
1990                                                      KGuiItem(i18n("Do &Not Sign")))) {
1991         case KMessageBox::Cancel:
1992             result = false;
1993             canceled = true;
1994             return false;
1995         case KMessageBox::ButtonCode::PrimaryAction:
1996             markAllAttachmentsForSigning(true);
1997             return true;
1998         case KMessageBox::ButtonCode::SecondaryAction:
1999             markAllAttachmentsForSigning(false);
2000             return false;
2001         default:
2002             qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2003             return false;
2004         }
2005         break;
2006     }
2007     case Kleo::Impossible: {
2008         KCursorSaver saver(Qt::WaitCursor);
2009         const QString msg = i18n(
2010             "You have requested to sign this message, "
2011             "but no valid signing keys have been configured "
2012             "for this identity.");
2013         if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18nc("@title:window", "Send Unsigned?"), KGuiItem(i18n("Send &Unsigned")))
2014             == KMessageBox::Cancel) {
2015             result = false;
2016             return false;
2017         } else {
2018             markAllAttachmentsForSigning(false);
2019             return false;
2020         }
2021     }
2022     }
2023 
2024     if (!sign || !doSignCompletely) {
2025         if (cryptoWarningUnsigned(currentIdentity())) {
2026             KCursorSaver saver(Qt::WaitCursor);
2027             const QString msg = sign && !doSignCompletely ? i18n(
2028                                     "Some parts of this message will not be signed.\n"
2029                                     "Sending only partially signed messages might violate site policy.\n"
2030                                     "Sign all parts instead?") // oh, I hate this...
2031                                                           : i18n(
2032                                                               "This message will not be signed.\n"
2033                                                               "Sending unsigned message might violate site policy.\n"
2034                                                               "Sign message instead?"); // oh, I hate this...
2035             const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
2036             switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2037                                                          msg,
2038                                                          i18nc("@title:window", "Unsigned-Message Warning"),
2039                                                          KGuiItem(buttonText),
2040                                                          KGuiItem(i18n("Send &As Is")))) {
2041             case KMessageBox::Cancel:
2042                 result = false;
2043                 canceled = true;
2044                 return false;
2045             case KMessageBox::ButtonCode::PrimaryAction:
2046                 markAllAttachmentsForSigning(true);
2047                 return true;
2048             case KMessageBox::ButtonCode::SecondaryAction:
2049                 return sign || doSignCompletely;
2050             default:
2051                 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2052                 return false;
2053             }
2054         }
2055     }
2056     return sign || doSignCompletely;
2057 }
2058 
2059 bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2060                                                  Kleo::KeyResolver *keyResolver,
2061                                                  bool encryptSomething,
2062                                                  bool signSomething,
2063                                                  bool &result,
2064                                                  bool &canceled)
2065 {
2066     bool encrypt = false;
2067     bool opportunistic = false;
2068     switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2069     case Kleo::DoIt:
2070         if (!encryptSomething) {
2071             markAllAttachmentsForEncryption(true);
2072             return true;
2073         }
2074         encrypt = true;
2075         break;
2076     case Kleo::DontDoIt:
2077         encrypt = false;
2078         break;
2079     case Kleo::AskOpportunistic:
2080         opportunistic = true;
2081         // fall through...
2082         [[fallthrough]];
2083     case Kleo::Ask: {
2084         // the user wants to be asked or has to be asked
2085         KCursorSaver saver(Qt::WaitCursor);
2086         const QString msg = opportunistic ? i18n(
2087                                 "Valid trusted encryption keys were found for all recipients.\n"
2088                                 "Encrypt this message?")
2089                                           : i18n(
2090                                               "Examination of the recipient's encryption preferences "
2091                                               "yielded that you be asked whether or not to encrypt "
2092                                               "this message.\n"
2093                                               "Encrypt this message?");
2094         switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2095                                                      msg,
2096                                                      i18n("Encrypt Message?"),
2097                                                      KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2098                                                      KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2099         case KMessageBox::Cancel:
2100             result = false;
2101             canceled = true;
2102             return false;
2103         case KMessageBox::ButtonCode::PrimaryAction:
2104             markAllAttachmentsForEncryption(true);
2105             return true;
2106         case KMessageBox::ButtonCode::SecondaryAction:
2107             markAllAttachmentsForEncryption(false);
2108             return false;
2109         default:
2110             qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2111             return false;
2112         }
2113         break;
2114     }
2115     case Kleo::Conflict: {
2116         // warn the user that there are conflicting encryption preferences
2117         KCursorSaver saver(Qt::WaitCursor);
2118         const QString msg = i18n(
2119             "There are conflicting encryption preferences "
2120             "for these recipients.\n"
2121             "Encrypt this message?");
2122         switch (KMessageBox::warningTwoActionsCancel(
2123 
2124             m_parentWidget,
2125             msg,
2126             i18n("Encrypt Message?"),
2127             KGuiItem(i18n("&Encrypt")),
2128             KGuiItem(i18n("Do &Not Encrypt")))) {
2129         case KMessageBox::Cancel:
2130             result = false;
2131             canceled = true;
2132             return false;
2133         case KMessageBox::ButtonCode::PrimaryAction:
2134             markAllAttachmentsForEncryption(true);
2135             return true;
2136         case KMessageBox::ButtonCode::SecondaryAction:
2137             markAllAttachmentsForEncryption(false);
2138             return false;
2139         default:
2140             qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2141             return false;
2142         }
2143         break;
2144     }
2145     case Kleo::Impossible: {
2146         KCursorSaver saver(Qt::WaitCursor);
2147         const QString msg = i18n(
2148             "You have requested to encrypt this message, "
2149             "and to encrypt a copy to yourself, "
2150             "but no valid trusted encryption keys have been "
2151             "configured for this identity.");
2152         if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18nc("@title:window", "Send Unencrypted?"), KGuiItem(i18n("Send &Unencrypted")))
2153             == KMessageBox::Cancel) {
2154             result = false;
2155             return false;
2156         } else {
2157             markAllAttachmentsForEncryption(false);
2158             return false;
2159         }
2160     }
2161     }
2162 
2163     if (!encrypt || !doEncryptCompletely) {
2164         if (cryptoWarningUnencrypted(currentIdentity())) {
2165             KCursorSaver saver(Qt::WaitCursor);
2166             const QString msg = !doEncryptCompletely ? i18n(
2167                                     "Some parts of this message will not be encrypted.\n"
2168                                     "Sending only partially encrypted messages might violate "
2169                                     "site policy and/or leak sensitive information.\n"
2170                                     "Encrypt all parts instead?") // oh, I hate this...
2171                                                      : i18n(
2172                                                          "This message will not be encrypted.\n"
2173                                                          "Sending unencrypted messages might violate site policy and/or "
2174                                                          "leak sensitive information.\n"
2175                                                          "Encrypt messages instead?"); // oh, I hate this...
2176             const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2177             switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2178                                                          msg,
2179                                                          i18nc("@title:window", "Unencrypted Message Warning"),
2180                                                          KGuiItem(buttonText),
2181                                                          KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2182             case KMessageBox::Cancel:
2183                 result = false;
2184                 canceled = true;
2185                 return false;
2186             case KMessageBox::ButtonCode::PrimaryAction:
2187                 markAllAttachmentsForEncryption(true);
2188                 return true;
2189             case KMessageBox::ButtonCode::SecondaryAction:
2190                 return encrypt || doEncryptCompletely;
2191             default:
2192                 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2193                 return false;
2194             }
2195         }
2196     }
2197 
2198     return encrypt || doEncryptCompletely;
2199 }
2200 
2201 void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2202 {
2203     mSendLaterInfo.reset(info);
2204 }
2205 
2206 SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2207 {
2208     return mSendLaterInfo.get();
2209 }
2210 
2211 void ComposerViewBase::addFollowupReminder(const QString &messageId)
2212 {
2213     if (!messageId.isEmpty()) {
2214         if (mFollowUpDate.isValid()) {
2215             auto job = new MessageComposer::FollowupReminderCreateJob;
2216             job->setSubject(m_subject);
2217             job->setMessageId(messageId);
2218             job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2219             job->setFollowUpReminderDate(mFollowUpDate);
2220             job->setCollectionToDo(mFollowUpCollection);
2221             job->start();
2222         }
2223     }
2224 }
2225 
2226 void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2227 {
2228     mSendLaterInfo->setItemId(item.id());
2229 
2230     auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2231     job->start();
2232 }
2233 
2234 bool ComposerViewBase::requestDeleveryConfirmation() const
2235 {
2236     return m_requestDeleveryConfirmation;
2237 }
2238 
2239 void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2240 {
2241     m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2242 }
2243 
2244 KMime::Message::Ptr ComposerViewBase::msg() const
2245 {
2246     return m_msg;
2247 }
2248 
2249 std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
2250 {
2251     if (!mExpiryChecker) {
2252         mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
2253                                                                                  encryptKeyNearExpiryWarningThresholdInDays(),
2254                                                                                  encryptRootCertNearExpiryWarningThresholdInDays(),
2255                                                                                  encryptChainCertNearExpiryWarningThresholdInDays()}});
2256     }
2257     return mExpiryChecker;
2258 }
2259 
2260 #include "moc_composerviewbase.cpp"