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"