Messagelib

composerviewbase.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
3 SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "composerviewbase.h"
9
10#include "attachment/attachmentcontrollerbase.h"
11#include "attachment/attachmentmodel.h"
12#include "composer-ng/richtextcomposerng.h"
13#include "composer-ng/richtextcomposersignatures.h"
14#include "composer.h"
15#include "composer/keyresolver.h"
16#include "composer/signaturecontroller.h"
17#include "draftstatus/draftstatus.h"
18#include "imagescaling/imagescalingutils.h"
19#include "job/emailaddressresolvejob.h"
20#include "part/globalpart.h"
21#include "part/infopart.h"
22#include "utils/kleo_util.h"
23#include "utils/util.h"
24#include "utils/util_p.h"
25#include <KPIMTextEdit/RichTextComposerControler>
26#include <KPIMTextEdit/RichTextComposerImages>
27
28#include "sendlater/sendlatercreatejob.h"
29#include "sendlater/sendlaterinfo.h"
30
31#include <PimCommonAkonadi/RecentAddresses>
32
33#include "settings/messagecomposersettings.h"
34#include <MessageComposer/RecipientsEditor>
35
36#include <KCursorSaver>
37#include <KIdentityManagementCore/Identity>
38#include <MimeTreeParser/ObjectTreeParser>
39#include <MimeTreeParser/SimpleObjectTreeSource>
40#include <Sonnet/DictionaryComboBox>
41
42#include <MessageCore/AutocryptStorage>
43#include <MessageCore/NodeHelper>
44#include <MessageCore/StringUtil>
45
46#include <Akonadi/MessageQueueJob>
47#include <MailTransport/TransportComboBox>
48#include <MailTransport/TransportManager>
49
50#include <Akonadi/CollectionComboBox>
51#include <Akonadi/CollectionFetchJob>
52#include <Akonadi/ItemCreateJob>
53#include <Akonadi/MessageFlags>
54#include <Akonadi/SpecialMailCollections>
55
56#include <KEmailAddress>
57#include <KIdentityManagementCore/IdentityManager>
58#include <KIdentityManagementWidgets/IdentityCombo>
59
60#include "messagecomposer_debug.h"
61
62#include <Libkleo/ExpiryChecker>
63#include <Libkleo/ExpiryCheckerSettings>
64
65#include <QGpgME/ExportJob>
66#include <QGpgME/ImportJob>
67#include <QGpgME/Protocol>
68#include <gpgme++/context.h>
69#include <gpgme++/importresult.h>
70
71#include <KLocalizedString>
72#include <KMessageBox>
73#include <QSaveFile>
74
75#include "followupreminder/followupremindercreatejob.h"
76#include <QDir>
77#include <QStandardPaths>
78#include <QTemporaryDir>
79#include <QTimer>
80#include <QUuid>
81
82using namespace MessageComposer;
83
84ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
85 : QObject(parent)
86 , m_msg(KMime::Message::Ptr(new KMime::Message))
87 , m_parentWidget(parentGui)
88 , m_cryptoMessageFormat(Kleo::AutoFormat)
89 , m_autoSaveInterval(60000) // default of 1 min
90{
91 m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set.
92
93 initAutoSave();
94}
95
96ComposerViewBase::~ComposerViewBase() = default;
97
99{
100 return !m_composers.isEmpty();
101}
102
103void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg, bool allowDecryption)
104{
105 if (m_attachmentModel) {
106 const auto attachments{m_attachmentModel->attachments()};
107 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
108 if (!m_attachmentModel->removeAttachment(attachment)) {
109 qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
110 }
111 }
112 }
113 m_msg = msg;
114 if (m_recipientsEditor) {
115 m_recipientsEditor->clear();
116 bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), MessageComposer::Recipient::To);
118 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc);
119 }
121 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc);
122 }
124 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), MessageComposer::Recipient::ReplyTo);
125 }
126 m_recipientsEditor->setFocusBottom();
127
129 // If we are loading from a draft, load unexpanded aliases as well
130 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-To")) {
131 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
132 for (const QString &addr : spl) {
133 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::To)) {
135 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
136 break;
137 }
138 }
139 }
140 }
142 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
143 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
144 for (const QString &addr : spl) {
145 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Cc)) {
146 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
148 break;
149 }
150 }
151 }
152 }
154 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
155 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
156 for (const QString &addr : spl) {
157 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
158 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
160 break;
161 }
162 }
163 }
164 }
166 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
167 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
168 for (const QString &addr : spl) {
169 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::ReplyTo)) {
170 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
172 break;
173 }
174 }
175 }
176 }
177 Q_EMIT tooManyRecipient(resultTooManyRecipients);
178 }
179 // First, we copy the message and then parse it to the object tree parser.
180 // The otp gets the message text out of it, in textualContent(), and also decrypts
181 // the message if necessary.
183 msgContent.setContent(m_msg->encodedContent());
184 msgContent.parse();
186 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
187 emptySource.setDecryptMessage(allowDecryption);
188 otp.parseObjectTree(&msgContent);
189
190 // Load the attachments
191 const auto attachmentsOfExtraContents{otp.nodeHelper()->attachmentsOfExtraContents()};
192 for (const auto &att : attachmentsOfExtraContents) {
193 addAttachmentPart(att);
194 }
195 const auto attachments{msgContent.attachments()};
196 for (const auto &att : attachments) {
197 addAttachmentPart(att);
198 }
199
200 int transportId = -1;
201 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
202 transportId = hdr->asUnicodeString().toInt();
203 }
204
205 if (m_transport) {
207 if (transport) {
208 if (!m_transport->setCurrentTransport(transport->id())) {
209 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to find transport id" << transport->id();
210 }
211 }
212 }
213
214 // Set the HTML text and collect HTML images
215 QString htmlContent = otp.htmlContent();
216 if (htmlContent.isEmpty()) {
217 m_editor->setPlainText(otp.plainTextContent());
218 } else {
219 // Bug 372085 <div id="name"> is replaced in qtextedit by <a id="name">... => break url
220 htmlContent.replace(QRegularExpression(QStringLiteral("<div\\s*id=\".*\">")), QStringLiteral("<div>"));
221 m_editor->setHtml(htmlContent);
222 Q_EMIT enableHtml();
223 collectImages(m_msg.data());
224 }
225
226 if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
227 m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
228 }
229}
230
231void ComposerViewBase::updateTemplate(const KMime::Message::Ptr &msg)
232{
233 // First, we copy the message and then parse it to the object tree parser.
234 // The otp gets the message text out of it, in textualContent(), and also decrypts
235 // the message if necessary.
237 msgContent.setContent(msg->encodedContent());
238 msgContent.parse();
240 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
241 otp.parseObjectTree(&msgContent);
242 // Set the HTML text and collect HTML images
243 if (!otp.htmlContent().isEmpty()) {
244 m_editor->setHtml(otp.htmlContent());
245 Q_EMIT enableHtml();
246 collectImages(msg.data());
247 } else {
248 m_editor->setPlainText(otp.plainTextContent());
249 }
250
251 if (auto hdr = msg->headerByType("X-KMail-CursorPos")) {
252 m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toInt());
253 }
254}
255
256void ComposerViewBase::saveMailSettings()
257{
258 const auto identity = currentIdentity();
259 auto header = new KMime::Headers::Generic("X-KMail-Transport");
260 header->fromUnicodeString(QString::number(m_transport->currentTransportId()), "utf-8");
261 m_msg->setHeader(header);
262
263 header = new KMime::Headers::Generic("X-KMail-Transport-Name");
264 header->fromUnicodeString(m_transport->currentText(), "utf-8");
265 m_msg->setHeader(header);
266
267 header = new KMime::Headers::Generic("X-KMail-Fcc");
268 header->fromUnicodeString(QString::number(m_fccCollection.id()), "utf-8");
269 m_msg->setHeader(header);
270
271 header = new KMime::Headers::Generic("X-KMail-Identity");
272 header->fromUnicodeString(QString::number(identity.uoid()), "utf-8");
273 m_msg->setHeader(header);
274
275 header = new KMime::Headers::Generic("X-KMail-Identity-Name");
276 header->fromUnicodeString(identity.identityName(), "utf-8");
277 m_msg->setHeader(header);
278
279 header = new KMime::Headers::Generic("X-KMail-Dictionary");
280 header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
281 m_msg->setHeader(header);
282
283 // Save the quote prefix which is used for this message. Each message can have
284 // a different quote prefix, for example depending on the original sender.
285 if (m_editor->quotePrefixName().isEmpty()) {
286 m_msg->removeHeader("X-KMail-QuotePrefix");
287 } else {
288 header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
289 header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
290 m_msg->setHeader(header);
291 }
292
293 if (m_editor->composerControler()->isFormattingUsed()) {
294 qCDebug(MESSAGECOMPOSER_LOG) << "HTML mode";
295 header = new KMime::Headers::Generic("X-KMail-Markup");
296 header->fromUnicodeString(QStringLiteral("true"), "utf-8");
297 m_msg->setHeader(header);
298 } else {
299 m_msg->removeHeader("X-KMail-Markup");
300 qCDebug(MESSAGECOMPOSER_LOG) << "Plain text";
301 }
302}
303
304void ComposerViewBase::clearFollowUp()
305{
306 mFollowUpDate = QDate();
307 mFollowUpCollection = Akonadi::Collection();
308}
309
310void ComposerViewBase::send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher)
311{
312 mSendMethod = method;
313 mSaveIn = saveIn;
314
316 const auto identity = currentIdentity();
317
318 if (identity.attachVcard() && m_attachmentController->attachOwnVcard()) {
319 const QString vcardFileName = identity.vCardFile();
320 if (!vcardFileName.isEmpty()) {
321 m_attachmentController->addAttachmentUrlSync(QUrl::fromLocalFile(vcardFileName));
322 }
323 }
324 saveMailSettings();
325
326 if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
327 const QString keepBtnText =
328 m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
329 const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
330 int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
331 i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
332 "<p>do you want to delete your markup?</p></qt>"),
333 i18nc("@title:window", "Sign/Encrypt Message?"),
336 if (KMessageBox::Cancel == ret) {
337 return;
338 }
339 if (KMessageBox::ButtonCode::SecondaryAction == ret) {
340 m_encrypt = false;
341 m_sign = false;
342 } else {
343 Q_EMIT disableHtml(NoConfirmationNeeded);
344 }
345 }
346
347 if (m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone) {
348 // we can't use the state of the mail itself, to remember the
349 // signing and encryption state, so let's add a header instead
350 DraftSignatureState(m_msg).setState(m_sign);
351 DraftEncryptionState(m_msg).setState(m_encrypt);
352 DraftCryptoMessageFormatState(m_msg).setState(m_cryptoMessageFormat);
353 } else {
354 removeDraftCryptoHeaders(m_msg);
355 }
356
357 if (mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) {
358 if (!MessageComposer::Util::sendMailDispatcherIsOnline(m_parentWidget)) {
359 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to set sendmaildispatcher online. Please verify it";
360 return;
361 }
362 }
363
364 readyForSending();
365}
366
367void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
368{
369 m_customHeader = customHeader;
370}
371
372void ComposerViewBase::readyForSending()
373{
374 qCDebug(MESSAGECOMPOSER_LOG) << "Entering readyForSending";
375 if (!m_msg) {
376 qCDebug(MESSAGECOMPOSER_LOG) << "m_msg == 0!";
377 return;
378 }
379
380 if (!m_composers.isEmpty()) {
381 // This may happen if e.g. the autosave timer calls applyChanges.
382 qCDebug(MESSAGECOMPOSER_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
383 return;
384 }
385
386 // first, expand all addresses
387 auto job = new MessageComposer::EmailAddressResolveJob(this);
388 const auto identity = currentIdentity();
389 if (!identity.isNull()) {
390 job->setDefaultDomainName(identity.defaultDomainName());
391 }
392 job->setFrom(from());
393 job->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
394 job->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
395 job->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
396 job->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
397
398 connect(job, &MessageComposer::EmailAddressResolveJob::result, this, &ComposerViewBase::slotEmailAddressResolved);
399 job->start();
400}
401
402void ComposerViewBase::slotEmailAddressResolved(KJob *job)
403{
404 if (job->error()) {
405 qCWarning(MESSAGECOMPOSER_LOG) << "An error occurred while resolving the email addresses:" << job->errorString();
406 // This error could be caused by a broken search infrastructure, so we ignore it for now
407 // to not block sending emails completely.
408 }
409
410 bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled();
411
413 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
414 mExpandedFrom = resolveJob->expandedFrom();
415 mExpandedTo = resolveJob->expandedTo();
416 mExpandedCc = resolveJob->expandedCc();
417 mExpandedBcc = resolveJob->expandedBcc();
418 mExpandedReplyTo = resolveJob->expandedReplyTo();
419 if (autoresizeImage) {
420 const QStringList listEmails = QStringList() << mExpandedFrom << mExpandedTo << mExpandedCc << mExpandedBcc << mExpandedReplyTo;
421 MessageComposer::Utils resizeUtils;
422 autoresizeImage = resizeUtils.filterRecipients(listEmails);
423 }
424 } else { // saved to draft, so keep the old values, not very nice.
425 mExpandedFrom = from();
426 const auto recipients{m_recipientsEditor->recipients()};
427 for (const MessageComposer::Recipient::Ptr &r : recipients) {
428 switch (r->type()) {
429 case MessageComposer::Recipient::To:
430 mExpandedTo << r->email();
431 break;
432 case MessageComposer::Recipient::Cc:
433 mExpandedCc << r->email();
434 break;
435 case MessageComposer::Recipient::Bcc:
436 mExpandedBcc << r->email();
437 break;
438 case MessageComposer::Recipient::ReplyTo:
439 mExpandedReplyTo << r->email();
440 break;
441 case MessageComposer::Recipient::Undefined:
442 Q_ASSERT(!"Unknown recipient type!");
443 break;
444 }
445 }
450 const auto expandedToLst{resolveJob->expandedTo()};
451 for (const QString &exp : expandedToLst) {
452 if (!mExpandedTo.contains(exp)) { // this address was expanded, so save it explicitly
453 unExpandedTo << exp;
454 }
455 }
456 const auto expandedCcLst{resolveJob->expandedCc()};
457 for (const QString &exp : expandedCcLst) {
458 if (!mExpandedCc.contains(exp)) {
459 unExpandedCc << exp;
460 }
461 }
462 const auto expandedBCcLst{resolveJob->expandedBcc()};
463 for (const QString &exp : expandedBCcLst) {
464 if (!mExpandedBcc.contains(exp)) { // this address was expanded, so save it explicitly
466 }
467 }
468 const auto expandedReplyLst{resolveJob->expandedReplyTo()};
469 for (const QString &exp : expandedReplyLst) {
470 if (!mExpandedReplyTo.contains(exp)) { // this address was expanded, so save it explicitly
472 }
473 }
474 auto header = new KMime::Headers::Generic("X-KMail-UnExpanded-To");
475 header->from7BitString(unExpandedTo.join(QLatin1StringView(", ")).toLatin1());
476 m_msg->setHeader(header);
477 header = new KMime::Headers::Generic("X-KMail-UnExpanded-CC");
478 header->from7BitString(unExpandedCc.join(QLatin1StringView(", ")).toLatin1());
479 m_msg->setHeader(header);
480 header = new KMime::Headers::Generic("X-KMail-UnExpanded-BCC");
481 header->from7BitString(unExpandedBcc.join(QLatin1StringView(", ")).toLatin1());
482 m_msg->setHeader(header);
483 header = new KMime::Headers::Generic("X-KMail-UnExpanded-Reply-To");
484 header->from7BitString(unExpandedReplyTo.join(QLatin1StringView(", ")).toLatin1());
485 m_msg->setHeader(header);
486 autoresizeImage = false;
487 }
488
489 Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
490 // checks for emptiness before calling it
491 // so just ensure it actually is empty
492 // and document it
493 // we first figure out if we need to create multiple messages with different crypto formats
494 // if so, we create a composer per format
495 // if we aren't signing or encrypting, this just returns a single empty message
496 if (m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone && !mSendLaterInfo) {
498 composer->setNoCrypto(true);
499 m_composers.append(composer);
500 } else {
501 bool wasCanceled = false;
502 m_composers = generateCryptoMessages(wasCanceled);
503 if (wasCanceled) {
504 return;
505 }
506 }
507
508 if (m_composers.isEmpty()) {
509 Q_EMIT failed(i18n("It was not possible to create a message composer."));
510 return;
511 }
512
513 if (autoresizeImage) {
514 if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) {
515 if (m_attachmentModel) {
516 MessageComposer::Utils resizeUtils;
517 if (resizeUtils.containsImage(m_attachmentModel->attachments())) {
518 const int rc = KMessageBox::warningTwoActions(m_parentWidget,
519 i18n("Do you want to resize images?"),
520 i18nc("@title:window", "Auto Resize Images"),
521 KGuiItem(i18nc("@action:button", "Auto Resize")),
522 KGuiItem(i18nc("@action:button", "Do Not Resize")));
523 if (rc == KMessageBox::ButtonCode::PrimaryAction) {
524 autoresizeImage = true;
525 } else {
526 autoresizeImage = false;
527 }
528 } else {
529 autoresizeImage = false;
530 }
531 }
532 }
533 }
534 // Compose each message and prepare it for queueing, sending, or storing
535
536 // working copy in case composers instantly emit result
537 const auto composers = m_composers;
539 fillComposer(composer, UseExpandedRecipients, autoresizeImage);
540 connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
541 composer->start();
542 qCDebug(MESSAGECOMPOSER_LOG) << "Started a composer for sending!";
543 }
544}
545
546namespace
547{
548// helper methods for reading encryption settings
549
550inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
551{
552 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
553 return Kleo::chrono::days{-1};
554 }
555 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
556 return Kleo::chrono::days{qMax(1, num)};
557}
558
559inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
560{
561 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
562 return Kleo::chrono::days{-1};
563 }
564 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
565 return Kleo::chrono::days{qMax(1, num)};
566}
567
568inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
569{
570 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
571 return Kleo::chrono::days{-1};
572 }
573 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
574 return Kleo::chrono::days{qMax(1, num)};
575}
576
577inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
578{
579 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
580 return Kleo::chrono::days{-1};
581 }
582 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
583 return Kleo::chrono::days{qMax(1, num)};
584}
585
586inline bool showKeyApprovalDialog()
587{
588 return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
589}
590
591inline bool cryptoWarningUnsigned(const KIdentityManagementCore::Identity &identity)
592{
593 if (identity.encryptionOverride()) {
594 return identity.warnNotSign();
595 }
596 return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned();
597}
598
599inline bool cryptoWarningUnencrypted(const KIdentityManagementCore::Identity &identity)
600{
601 if (identity.encryptionOverride()) {
602 return identity.warnNotEncrypt();
603 }
604 return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted();
605}
606} // nameless namespace
607
608bool ComposerViewBase::addKeysToContext(const QString &gnupgHome,
609 const QList<QPair<QStringList, std::vector<GpgME::Key>>> &data,
610 const std::map<QByteArray, QString> &autocryptMap)
611{
612 bool needSpecialContext = false;
613
614 for (const auto &p : data) {
615 for (const auto &k : p.second) {
616 const auto it = autocryptMap.find(k.primaryFingerprint());
617 if (it != autocryptMap.end()) {
618 needSpecialContext = true;
619 break;
620 }
621 }
622 if (needSpecialContext) {
623 break;
624 }
625 }
626
627 if (!needSpecialContext) {
628 return false;
629 }
630 const QGpgME::Protocol *proto(QGpgME::openpgp());
631
632 const auto storage = MessageCore::AutocryptStorage::self();
633 QEventLoop loop;
634 int runningJobs = 0;
635 for (const auto &p : data) {
636 for (const auto &k : p.second) {
637 const auto it = autocryptMap.find(k.primaryFingerprint());
638 if (it == autocryptMap.end()) {
639 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "via Export/Import";
640 auto exportJob = proto->publicKeyExportJob(false);
642 &QGpgME::ExportJob::result,
643 this,
644 [&gnupgHome, &proto, &runningJobs, &loop, &k](const GpgME::Error &result,
645 const QByteArray &keyData,
646 const QString &auditLogAsHtml,
647 const GpgME::Error &auditLogError) {
648 Q_UNUSED(auditLogAsHtml);
649 Q_UNUSED(auditLogError);
650 if (result) {
651 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to export " << k.primaryFingerprint() << result.asString();
652 --runningJobs;
653 if (runningJobs < 1) {
654 loop.quit();
655 }
656 }
657
658 auto importJob = proto->importJob();
659 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
660 importJob->exec(keyData);
661 importJob->deleteLater();
662 --runningJobs;
663 if (runningJobs < 1) {
664 loop.quit();
665 }
666 });
667 QStringList patterns;
668 patterns << QString::fromUtf8(k.primaryFingerprint());
669 runningJobs++;
670 exportJob->start(patterns);
671 exportJob->setExportFlags(GpgME::Context::ExportMinimal);
672 } else {
673 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage";
674 const auto recipient = storage->getRecipient(it->second.toUtf8());
675 auto key = recipient->gpgKey();
676 auto keydata = recipient->gpgKeydata();
677 if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) {
678 qCDebug(MESSAGECOMPOSER_LOG) << "Using gossipkey";
679 keydata = recipient->gossipKeydata();
680 }
681 auto importJob = proto->importJob();
682 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
683 const auto result = importJob->exec(keydata);
684 importJob->deleteLater();
685 }
686 }
687 }
688 loop.exec();
689 return true;
690}
691
692void ComposerViewBase::setAkonadiLookupEnabled(bool akonadiLookupEnabled)
693{
694 m_akonadiLookupEnabled = akonadiLookupEnabled;
695}
696
697QList<MessageComposer::Composer *> ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
698{
699 const auto id = currentIdentity();
700
701 bool canceled = false;
702
703 qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
704 connect(expiryChecker().get(),
705 &Kleo::ExpiryChecker::expiryMessage,
706 this,
707 [&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
708 if (!isNewMessage) {
709 return;
710 }
711
712 if (canceled) {
713 return;
714 }
715 QString title;
717 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
718 dontAskAgainName = QStringLiteral("own key expires soon warning");
719 } else {
720 dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
721 }
722 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
723 title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
724 } else {
725 title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
726 }
729 canceled = true;
730 }
731 });
732
734 new Kleo::KeyResolver(true, showKeyApprovalDialog(), id.pgpAutoEncrypt(), m_cryptoMessageFormat, expiryChecker()));
735
736 keyResolver->setAutocryptEnabled(autocryptEnabled());
737 keyResolver->setAkonadiLookupEnabled(m_akonadiLookupEnabled);
738
740 QStringList signKeys;
741
742 bool signSomething = m_sign;
743 bool doSignCompletely = m_sign;
744 bool encryptSomething = m_encrypt;
745 bool doEncryptCompletely = m_encrypt;
746
747 // Add encryptionkeys from id to keyResolver
748 if (!id.pgpEncryptionKey().isEmpty()) {
749 encryptToSelfKeys.push_back(QLatin1StringView(id.pgpEncryptionKey()));
750 }
751 if (!id.smimeEncryptionKey().isEmpty()) {
752 encryptToSelfKeys.push_back(QLatin1StringView(id.smimeEncryptionKey()));
753 }
754 if (canceled || keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
755 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
756 return {};
757 }
758
759 // Add signingkeys from id to keyResolver
760 if (!id.pgpSigningKey().isEmpty()) {
761 signKeys.push_back(QLatin1StringView(id.pgpSigningKey()));
762 }
763 if (!id.smimeSigningKey().isEmpty()) {
764 signKeys.push_back(QLatin1StringView(id.smimeSigningKey()));
765 }
766 if (canceled || keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
767 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
768 return {};
769 }
770
771 if (m_attachmentModel) {
772 const auto attachments = m_attachmentModel->attachments();
773 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
774 if (attachment->isSigned()) {
775 signSomething = true;
776 } else {
777 doEncryptCompletely = false;
778 }
779 if (attachment->isEncrypted()) {
780 encryptSomething = true;
781 } else {
782 doSignCompletely = false;
783 }
784 }
785 }
786
787 const QStringList recipients = mExpandedTo + mExpandedCc;
788 const QStringList bcc(mExpandedBcc);
789
790 keyResolver->setPrimaryRecipients(recipients);
791 keyResolver->setSecondaryRecipients(bcc);
792
793 bool result = true;
794 canceled = false;
795 signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
796 if (!result) {
797 // TODO handle failure
798 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
799 if (!canceled) {
800 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
801 } else {
803 }
804 wasCanceled = canceled;
805 return {};
806 }
807
808 canceled = false;
809 encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
810 if (!result) {
811 // TODO handle failure
812 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
813 if (!canceled) {
814 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
815 } else {
817 }
818
819 wasCanceled = canceled;
820 return {};
821 }
822
824
825 // No encryption or signing is needed
828 if (m_cryptoMessageFormat & Kleo::OpenPGPMIMEFormat) {
829 composer->setAutocryptEnabled(autocryptEnabled());
830 if (keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat).size() > 0) {
831 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]);
832 }
833 }
834 composers.append(composer);
835 return composers;
836 }
837
838 canceled = false;
839 const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
840 if (kpgpResult == Kleo::Canceled || canceled) {
841 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
842 return {};
843 } else if (kpgpResult != Kleo::Ok) {
844 // TODO handle failure
845 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
846 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
847 return {};
848 }
849
850 qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys.";
851
853 Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
854 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
855 concreteFormat = concreteCryptoMessageFormats[i];
856 const auto encData = keyResolver->encryptionItems(concreteFormat);
857 if (encData.empty()) {
858 continue;
859 }
860
861 if (!(concreteFormat & m_cryptoMessageFormat)) {
862 continue;
863 }
864
866
867 if (encryptSomething || autocryptEnabled()) {
868 auto end(encData.end());
870 data.reserve(encData.size());
871 for (auto it = encData.begin(); it != end; ++it) {
872 QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
873 data.append(p);
874 qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
875 }
876 composer->setEncryptionKeys(data);
877 if (concreteFormat & Kleo::OpenPGPMIMEFormat && autocryptEnabled()) {
878 composer->setAutocryptEnabled(autocryptEnabled());
879 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
881 bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
882 if (specialGnupgHome) {
883 dir.setAutoRemove(false);
884 composer->setGnupgHome(dir.path());
885 }
886 }
887 }
888
889 if (signSomething) {
890 // find signing keys for this format
891 std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
892 composer->setSigningKeys(signingKeys);
893 }
894
895 composer->setCryptoMessageFormat(concreteFormat);
896 composer->setSignAndEncrypt(signSomething, encryptSomething);
897
898 composers.append(composer);
899 }
900 } else {
902 composers.append(composer);
903 // If we canceled sign or encrypt be sure to change status in attachment.
904 markAllAttachmentsForSigning(false);
905 markAllAttachmentsForEncryption(false);
906 }
907
908 if (composers.isEmpty() && (signSomething || encryptSomething)) {
909 Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
910 }
911
912 return composers;
913}
914
915void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
916{
917 globalPart->setParentWidgetForGui(m_parentWidget);
918 globalPart->setCharsets(m_charsets);
919 globalPart->setMDNRequested(m_mdnRequested);
920 globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
921}
922
923void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
924{
925 // TODO splitAddressList and expandAliases ugliness should be handled by a
926 // special AddressListEdit widget... (later: see RecipientsEditor)
927
928 if (m_fccCombo) {
929 infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
930 } else {
931 if (m_fccCollection.isValid()) {
932 infoPart->setFcc(QString::number(m_fccCollection.id()));
933 }
934 }
935
936 infoPart->setTransportId(m_transport->currentTransportId());
937 if (expansion == UseExpandedRecipients) {
938 infoPart->setFrom(mExpandedFrom);
939 infoPart->setTo(mExpandedTo);
940 infoPart->setCc(mExpandedCc);
941 infoPart->setBcc(mExpandedBcc);
942 infoPart->setReplyTo(mExpandedReplyTo);
943 } else {
944 infoPart->setFrom(from());
945 infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
946 infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
947 infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
948 infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
949 }
950 infoPart->setSubject(subject());
951 infoPart->setUserAgent(QStringLiteral("KMail"));
952 infoPart->setUrgent(m_urgent);
953
954 if (auto inReplyTo = m_msg->inReplyTo(false)) {
955 infoPart->setInReplyTo(inReplyTo->asUnicodeString());
956 }
957
958 if (auto references = m_msg->references(false)) {
959 infoPart->setReferences(references->asUnicodeString());
960 }
961
963 if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
964 extras << hdr;
965 }
966 if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
967 extras << hdr;
968 }
969 if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
970 extras << hdr;
971 }
972 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
973 extras << hdr;
974 }
975 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
976 extras << hdr;
977 }
978 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
979 extras << hdr;
980 }
981 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
982 extras << hdr;
983 }
984 if (auto hdr = m_msg->organization(false)) {
985 extras << hdr;
986 }
987 if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
988 extras << hdr;
989 }
990 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
991 extras << hdr;
992 }
993 if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
994 extras << hdr;
995 }
996 if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
997 extras << hdr;
998 }
999 if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
1000 extras << hdr;
1001 }
1002 if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
1003 extras << hdr;
1004 }
1005 if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
1006 extras << hdr;
1007 }
1008 if (auto hdr = m_msg->headerByType("X-Face")) {
1009 extras << hdr;
1010 }
1011 if (auto hdr = m_msg->headerByType("Face")) {
1012 extras << hdr;
1013 }
1014 if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
1015 extras << hdr;
1016 }
1017 if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
1018 extras << hdr;
1019 }
1020 if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1021 extras << hdr;
1022 }
1023
1024 infoPart->setExtraHeaders(extras);
1025}
1026
1027void ComposerViewBase::slotSendComposeResult(KJob *job)
1028{
1029 Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1030 auto composer = static_cast<MessageComposer::Composer *>(job);
1031 if (composer->error() != MessageComposer::Composer::NoError) {
1032 qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1033 }
1034
1035 if (composer->error() == MessageComposer::Composer::NoError) {
1036 Q_ASSERT(m_composers.contains(composer));
1037 // The messages were composed successfully.
1038 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1039 const int numberOfMessage(composer->resultMessages().size());
1040 for (int i = 0; i < numberOfMessage; ++i) {
1041 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1042 queueMessage(composer->resultMessages().at(i), composer);
1043 } else {
1044 saveMessage(composer->resultMessages().at(i), mSaveIn);
1045 }
1046 }
1047 saveRecentAddresses(composer->resultMessages().at(0));
1048 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1049 // The job warned the user about something, and the user chose to return
1050 // to the message. Nothing to do.
1051 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1052 Q_EMIT failed(i18n("Job cancelled by the user"));
1053 } else {
1054 qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1055 QString msg;
1056 if (composer->error() == MessageComposer::Composer::BugError) {
1057 msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1058 } else {
1059 msg = i18n("Could not compose message: %1", job->errorString());
1060 }
1061 Q_EMIT failed(msg);
1062 }
1063
1064 if (!composer->gnupgHome().isEmpty()) {
1065 QDir dir(composer->gnupgHome());
1066 dir.removeRecursively();
1067 }
1068
1069 m_composers.removeAll(composer);
1070}
1071
1072void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1073{
1074 KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1075 if (auto to = msg->to(false)) {
1076 const auto toAddresses = to->mailboxes();
1077 for (const auto &address : toAddresses) {
1078 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1079 }
1080 }
1081 if (auto cc = msg->cc(false)) {
1082 const auto ccAddresses = cc->mailboxes();
1083 for (const auto &address : ccAddresses) {
1084 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1085 }
1086 }
1087 if (auto bcc = msg->bcc(false)) {
1088 const auto bccAddresses = bcc->mailboxes();
1089 for (const auto &address : bccAddresses) {
1090 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1091 }
1092 }
1093}
1094
1095void 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(
1120 } else {
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);
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
1150void 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 const 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());
1173 }
1174}
1175
1176void ComposerViewBase::initAutoSave()
1177{
1178 qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
1179
1180 // Ensure that the autosave directory exists.
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
1193}
1194
1195Akonadi::Collection ComposerViewBase::followUpCollection() const
1196{
1197 return mFollowUpCollection;
1198}
1199
1200void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1201{
1202 mFollowUpCollection = followUpCollection;
1203}
1204
1205QDate ComposerViewBase::followUpDate() const
1206{
1207 return mFollowUpDate;
1208}
1209
1210void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1211{
1212 mFollowUpDate = followUpDate;
1213}
1214
1215Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1216{
1217 return m_dictionary;
1218}
1219
1220void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1221{
1222 m_dictionary = dictionary;
1223}
1224
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 {
1237 }
1238 }
1239 m_autoSaveTimer->start(m_autoSaveInterval);
1240 }
1241}
1242
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
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//-----------------------------------------------------------------------------
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
1294{
1295 m_autoSaveUUID = fileName;
1296
1297 Q_EMIT modified(true);
1298}
1299
1300void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
1301{
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) {
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
1332void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1333{
1336 const QString filename = autosavePath + m_autoSaveUUID;
1337 QSaveFile file(filename);
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
1378void 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);
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
1414 break;
1415 case MessageComposer::MessageSender::SaveInNone:
1416 break;
1417 }
1418
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
1431void 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 {
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
1450Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1451{
1452 Akonadi::Collection target;
1453 switch (mSaveIn) {
1454 case MessageComposer::MessageSender::SaveInNone:
1455 break;
1456 case MessageComposer::MessageSender::SaveInDrafts:
1458 break;
1459 case MessageComposer::MessageSender::SaveInTemplates:
1461 break;
1462 case MessageComposer::MessageSender::SaveInOutbox:
1464 break;
1465 }
1466
1467 return target;
1468}
1469
1470void 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) {
1494 }
1495}
1496
1497void 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
1508void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1509{
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
1523void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1524{
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
1555void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1556{
1557 fillComposer(composer, UseUnExpandedRecipients, false);
1558}
1559
1560void 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//-----------------------------------------------------------------------------
1572{
1573 if (m_recipientsEditor) {
1574 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1575 }
1576 return {};
1577}
1578
1579//-----------------------------------------------------------------------------
1580QString 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//-----------------------------------------------------------------------------
1589QString ComposerViewBase::bcc() const
1590{
1591 if (m_recipientsEditor) {
1592 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1593 }
1594 return {};
1595}
1596
1597QString ComposerViewBase::from() const
1598{
1599 return MessageComposer::Util::cleanedUpHeaderString(m_from);
1600}
1601
1602QString ComposerViewBase::replyTo() const
1603{
1604 if (m_recipientsEditor) {
1605 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1606 }
1607 return {};
1608}
1609
1610QString ComposerViewBase::subject() const
1611{
1612 return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1613}
1614
1615const KIdentityManagementCore::Identity &ComposerViewBase::currentIdentity() const
1616{
1617 return m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1618}
1619
1620bool ComposerViewBase::autocryptEnabled() const
1621{
1622 return currentIdentity().autocryptEnabled();
1623}
1624
1625void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1626{
1627 m_parentWidget = w;
1628}
1629
1630void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1631{
1632 m_attachmentController = controller;
1633}
1634
1635MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1636{
1637 return m_attachmentController;
1638}
1639
1641{
1642 m_attachmentModel = model;
1643}
1644
1645MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1646{
1647 return m_attachmentModel;
1648}
1649
1650void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1651{
1652 m_recipientsEditor = recEditor;
1653}
1654
1655MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1656{
1657 return m_recipientsEditor;
1658}
1659
1660void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1661{
1662 m_signatureController = sigController;
1663}
1664
1665MessageComposer::SignatureController *ComposerViewBase::signatureController()
1666{
1667 return m_signatureController;
1668}
1669
1670void ComposerViewBase::setIdentityCombo(KIdentityManagementWidgets::IdentityCombo *identCombo)
1671{
1672 m_identityCombo = identCombo;
1673}
1674
1675KIdentityManagementWidgets::IdentityCombo *ComposerViewBase::identityCombo()
1676{
1677 return m_identityCombo;
1678}
1679
1680void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident,
1681 const KIdentityManagementCore::Identity &oldIdent,
1682 MessageComposer::Recipient::Type type)
1683{
1686 switch (type) {
1687 case MessageComposer::Recipient::Bcc: {
1688 oldIdentList = oldIdent.bcc();
1689 newIdentList = ident.bcc();
1690 break;
1691 }
1692 case MessageComposer::Recipient::Cc: {
1693 oldIdentList = oldIdent.cc();
1694 newIdentList = ident.cc();
1695 break;
1696 }
1697 case MessageComposer::Recipient::ReplyTo: {
1698 oldIdentList = oldIdent.replyToAddr();
1699 newIdentList = ident.replyToAddr();
1700 break;
1701 }
1702 case MessageComposer::Recipient::To:
1703 case MessageComposer::Recipient::Undefined:
1704 return;
1705 }
1706
1707 if (oldIdentList != newIdentList) {
1709 for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1710 m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1711 }
1712
1714 for (const KMime::Types::Mailbox &recipient : newRecipients) {
1715 m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1716 }
1717 m_recipientsEditor->setFocusBottom();
1718 }
1719}
1720
1721void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
1722{
1723 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1724 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1725 updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1726
1729 // replace existing signatures
1730 const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1731 // Just append the signature if there was no old signature
1732 if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1733 signatureController()->applySignature(newSig);
1734 }
1735 const QString vcardFileName = ident.vCardFile();
1736 attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1737 attachmentController()->setAttachOwnVcard(ident.attachVcard());
1738
1739 m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1740}
1741
1742void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1743{
1744 m_editor = editor;
1745 m_editor->document()->setModified(false);
1746}
1747
1748MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1749{
1750 return m_editor;
1751}
1752
1753void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1754{
1755 m_transport = transpCombo;
1756}
1757
1758MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1759{
1760 return m_transport;
1761}
1762
1763void ComposerViewBase::setIdentityManager(KIdentityManagementCore::IdentityManager *identMan)
1764{
1765 m_identMan = identMan;
1766}
1767
1768KIdentityManagementCore::IdentityManager *ComposerViewBase::identityManager()
1769{
1770 return m_identMan;
1771}
1772
1773void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1774{
1775 if (m_fccCombo) {
1777 } else {
1778 m_fccCollection = fccCollection;
1779 }
1781 connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1782}
1783
1784void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1785{
1786 if (job->error()) {
1787 qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1789 if (m_fccCombo) {
1790 m_fccCombo->setDefaultCollection(sentMailCol);
1791 } else {
1792 m_fccCollection = sentMailCol;
1793 }
1794 }
1795}
1796
1797void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1798{
1799 m_fccCombo = fcc;
1800}
1801
1802Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1803{
1804 return m_fccCombo;
1805}
1806
1808{
1809 m_from = from;
1810}
1811
1812void ComposerViewBase::setSubject(const QString &subject)
1813{
1814 m_subject = subject;
1815 if (mSendLaterInfo) {
1816 mSendLaterInfo->setSubject(m_subject);
1817 mSendLaterInfo->setTo(to());
1818 }
1819}
1820
1821void ComposerViewBase::setAutoSaveInterval(int interval)
1822{
1823 m_autoSaveInterval = interval;
1824}
1825
1826void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1827{
1828 m_sign = sign;
1829 m_encrypt = encrypt;
1830 m_cryptoMessageFormat = format;
1831 m_neverEncrypt = neverEncryptDrafts;
1832}
1833
1834void ComposerViewBase::setCharsets(const QList<QByteArray> &charsets)
1835{
1836 m_charsets = charsets;
1837}
1838
1839void ComposerViewBase::setMDNRequested(bool mdnRequested)
1840{
1841 m_mdnRequested = mdnRequested;
1842}
1843
1844void ComposerViewBase::setUrgent(bool urgent)
1845{
1846 m_urgent = urgent;
1847}
1848
1849int ComposerViewBase::autoSaveInterval() const
1850{
1851 return m_autoSaveInterval;
1852}
1853
1854//-----------------------------------------------------------------------------
1855void ComposerViewBase::collectImages(KMime::Content *root)
1856{
1857 if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1858 KMime::Content *parentnode = n->parent();
1859 if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1861 while (node) {
1862 if (node->contentType()->isImage()) {
1863 qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1864 QImage img;
1865 img.loadFromData(node->decodedContent());
1866 m_editor->composerControler()->composerImages()->loadImage(
1867 img,
1868 QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1869 node->contentType()->name());
1870 }
1872 }
1873 }
1874 }
1875}
1876
1877//-----------------------------------------------------------------------------
1878bool ComposerViewBase::inlineSigningEncryptionSelected() const
1879{
1880 if (!m_sign && !m_encrypt) {
1881 return false;
1882 }
1883 return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1884}
1885
1886bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1887{
1888 if (attachmentKeywords.isEmpty()) {
1889 return false;
1890 }
1891 if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1892 return false;
1893 }
1894
1895 return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1896}
1897
1898ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1899{
1900 if (!hasMissingAttachments(attachmentKeywords)) {
1901 return NoMissingAttachmentFound;
1902 }
1903 const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
1904
1905 i18n("The message you have composed seems to refer to an "
1906 "attached file but you have not attached anything.\n"
1907 "Do you want to attach a file to your message?"),
1908 i18nc("@title:window", "File Attachment Reminder"),
1909 KGuiItem(i18nc("@action:button", "&Attach File..."), QLatin1StringView("mail-attachment")),
1910 KGuiItem(i18nc("@action:button", "&Send as Is"), QLatin1StringView("mail-send")));
1911 if (rc == KMessageBox::Cancel) {
1912 return FoundMissingAttachmentAndCancel;
1913 }
1914 if (rc == KMessageBox::ButtonCode::PrimaryAction) {
1915 m_attachmentController->showAddAttachmentFileDialog();
1916 return FoundMissingAttachmentAndAddedAttachment;
1917 }
1918
1919 return FoundMissingAttachmentAndSending;
1920}
1921
1922void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1923{
1924 if (m_attachmentModel) {
1925 const auto attachments = m_attachmentModel->attachments();
1926 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1927 attachment->setSigned(sign);
1928 }
1929 }
1930}
1931
1932void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1933{
1934 if (m_attachmentModel) {
1935 const auto attachments = m_attachmentModel->attachments();
1936 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1937 attachment->setEncrypted(encrypt);
1938 }
1939 }
1940}
1941
1942bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1943{
1944 bool sign = false;
1945 switch (keyResolver->checkSigningPreferences(signSomething)) {
1946 case Kleo::DoIt:
1947 if (!signSomething) {
1948 markAllAttachmentsForSigning(true);
1949 return true;
1950 }
1951 sign = true;
1952 break;
1953 case Kleo::DontDoIt:
1954 sign = false;
1955 break;
1956 case Kleo::AskOpportunistic:
1957 assert(0);
1958 case Kleo::Ask: {
1959 // the user wants to be asked or has to be asked
1961 const QString msg = i18n(
1962 "Examination of the recipient's signing preferences "
1963 "yielded that you be asked whether or not to sign "
1964 "this message.\n"
1965 "Sign this message?");
1966 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1967 msg,
1968 i18nc("@title:window", "Sign Message?"),
1969 KGuiItem(i18nc("to sign", "&Sign")),
1970 KGuiItem(i18nc("@action:button", "Do &Not Sign")))) {
1972 result = false;
1973 canceled = true;
1974 return false;
1975 case KMessageBox::ButtonCode::PrimaryAction:
1976 markAllAttachmentsForSigning(true);
1977 return true;
1978 case KMessageBox::ButtonCode::SecondaryAction:
1979 markAllAttachmentsForSigning(false);
1980 return false;
1981 default:
1982 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1983 return false;
1984 }
1985 break;
1986 }
1987 case Kleo::Conflict: {
1988 // warn the user that there are conflicting signing preferences
1990 const QString msg = i18n(
1991 "There are conflicting signing preferences "
1992 "for these recipients.\n"
1993 "Sign this message?");
1994 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1995 msg,
1996 i18nc("@title:window", "Sign Message?"),
1997 KGuiItem(i18nc("to sign", "&Sign")),
1998 KGuiItem(i18nc("@action:button", "Do &Not Sign")))) {
2000 result = false;
2001 canceled = true;
2002 return false;
2003 case KMessageBox::ButtonCode::PrimaryAction:
2004 markAllAttachmentsForSigning(true);
2005 return true;
2006 case KMessageBox::ButtonCode::SecondaryAction:
2007 markAllAttachmentsForSigning(false);
2008 return false;
2009 default:
2010 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2011 return false;
2012 }
2013 break;
2014 }
2015 case Kleo::Impossible: {
2017 const QString msg = i18n(
2018 "You have requested to sign this message, "
2019 "but no valid signing keys have been configured "
2020 "for this identity.");
2021 if (KMessageBox::warningContinueCancel(m_parentWidget,
2022 msg,
2023 i18nc("@title:window", "Send Unsigned?"),
2024 KGuiItem(i18nc("@action:button", "Send &Unsigned")))
2026 result = false;
2027 return false;
2028 } else {
2029 markAllAttachmentsForSigning(false);
2030 return false;
2031 }
2032 }
2033 }
2034
2035 if (!sign || !doSignCompletely) {
2036 if (cryptoWarningUnsigned(currentIdentity())) {
2038 const QString msg = sign && !doSignCompletely ? i18n(
2039 "Some parts of this message will not be signed.\n"
2040 "Sending only partially signed messages might violate site policy.\n"
2041 "Sign all parts instead?") // oh, I hate this...
2042 : i18n(
2043 "This message will not be signed.\n"
2044 "Sending unsigned message might violate site policy.\n"
2045 "Sign message instead?"); // oh, I hate this...
2046 const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
2047 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2048 msg,
2049 i18nc("@title:window", "Unsigned-Message Warning"),
2050 KGuiItem(buttonText),
2051 KGuiItem(i18nc("@action:button", "Send &As Is")))) {
2053 result = false;
2054 canceled = true;
2055 return false;
2056 case KMessageBox::ButtonCode::PrimaryAction:
2057 markAllAttachmentsForSigning(true);
2058 return true;
2059 case KMessageBox::ButtonCode::SecondaryAction:
2060 return sign || doSignCompletely;
2061 default:
2062 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2063 return false;
2064 }
2065 }
2066 }
2067 return sign || doSignCompletely;
2068}
2069
2070bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2071 Kleo::KeyResolver *keyResolver,
2072 bool encryptSomething,
2073 bool signSomething,
2074 bool &result,
2075 bool &canceled)
2076{
2077 bool encrypt = false;
2078 bool opportunistic = false;
2079 switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2080 case Kleo::DoIt:
2081 if (!encryptSomething) {
2082 markAllAttachmentsForEncryption(true);
2083 return true;
2084 }
2085 encrypt = true;
2086 break;
2087 case Kleo::DontDoIt:
2088 encrypt = false;
2089 break;
2090 case Kleo::AskOpportunistic:
2091 opportunistic = true;
2092 // fall through...
2093 [[fallthrough]];
2094 case Kleo::Ask: {
2095 // the user wants to be asked or has to be asked
2097 const QString msg = opportunistic ? i18n(
2098 "Valid trusted encryption keys were found for all recipients.\n"
2099 "Encrypt this message?")
2100 : i18n(
2101 "Examination of the recipient's encryption preferences "
2102 "yielded that you be asked whether or not to encrypt "
2103 "this message.\n"
2104 "Encrypt this message?");
2105 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2106 msg,
2107 i18n("Encrypt Message?"),
2108 KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2109 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2111 result = false;
2112 canceled = true;
2113 return false;
2114 case KMessageBox::ButtonCode::PrimaryAction:
2115 markAllAttachmentsForEncryption(true);
2116 return true;
2117 case KMessageBox::ButtonCode::SecondaryAction:
2118 markAllAttachmentsForEncryption(false);
2119 return false;
2120 default:
2121 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2122 return false;
2123 }
2124 break;
2125 }
2126 case Kleo::Conflict: {
2127 // warn the user that there are conflicting encryption preferences
2129 const QString msg = i18n(
2130 "There are conflicting encryption preferences "
2131 "for these recipients.\n"
2132 "Encrypt this message?");
2134
2135 m_parentWidget,
2136 msg,
2137 i18n("Encrypt Message?"),
2138 KGuiItem(i18nc("@action:button", "&Encrypt")),
2139 KGuiItem(i18nc("@action:button", "Do &Not Encrypt")))) {
2141 result = false;
2142 canceled = true;
2143 return false;
2144 case KMessageBox::ButtonCode::PrimaryAction:
2145 markAllAttachmentsForEncryption(true);
2146 return true;
2147 case KMessageBox::ButtonCode::SecondaryAction:
2148 markAllAttachmentsForEncryption(false);
2149 return false;
2150 default:
2151 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2152 return false;
2153 }
2154 break;
2155 }
2156 case Kleo::Impossible: {
2158 const QString msg = i18n(
2159 "You have requested to encrypt this message, "
2160 "and to encrypt a copy to yourself, "
2161 "but no valid trusted encryption keys have been "
2162 "configured for this identity.");
2163 if (KMessageBox::warningContinueCancel(m_parentWidget,
2164 msg,
2165 i18nc("@title:window", "Send Unencrypted?"),
2166 KGuiItem(i18nc("@action:button", "Send &Unencrypted")))
2168 result = false;
2169 return false;
2170 } else {
2171 markAllAttachmentsForEncryption(false);
2172 return false;
2173 }
2174 }
2175 }
2176
2177 if (!encrypt || !doEncryptCompletely) {
2178 if (cryptoWarningUnencrypted(currentIdentity())) {
2180 const QString msg = !doEncryptCompletely ? i18n(
2181 "Some parts of this message will not be encrypted.\n"
2182 "Sending only partially encrypted messages might violate "
2183 "site policy and/or leak sensitive information.\n"
2184 "Encrypt all parts instead?") // oh, I hate this...
2185 : i18n(
2186 "This message will not be encrypted.\n"
2187 "Sending unencrypted messages might violate site policy and/or "
2188 "leak sensitive information.\n"
2189 "Encrypt messages instead?"); // oh, I hate this...
2190 const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2191 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2192 msg,
2193 i18nc("@title:window", "Unencrypted Message Warning"),
2194 KGuiItem(buttonText),
2195 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2197 result = false;
2198 canceled = true;
2199 return false;
2200 case KMessageBox::ButtonCode::PrimaryAction:
2201 markAllAttachmentsForEncryption(true);
2202 return true;
2203 case KMessageBox::ButtonCode::SecondaryAction:
2204 return encrypt || doEncryptCompletely;
2205 default:
2206 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2207 return false;
2208 }
2209 }
2210 }
2211
2212 return encrypt || doEncryptCompletely;
2213}
2214
2215void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2216{
2217 mSendLaterInfo.reset(info);
2218}
2219
2220SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2221{
2222 return mSendLaterInfo.get();
2223}
2224
2225void ComposerViewBase::addFollowupReminder(const QString &messageId)
2226{
2227 if (!messageId.isEmpty()) {
2228 if (mFollowUpDate.isValid()) {
2230 job->setSubject(m_subject);
2231 job->setMessageId(messageId);
2232 job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2233 job->setFollowUpReminderDate(mFollowUpDate);
2234 job->setCollectionToDo(mFollowUpCollection);
2235 job->start();
2236 }
2237 }
2238}
2239
2240void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2241{
2242 mSendLaterInfo->setItemId(item.id());
2243
2244 auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2245 job->start();
2246}
2247
2248bool ComposerViewBase::requestDeleveryConfirmation() const
2249{
2250 return m_requestDeleveryConfirmation;
2251}
2252
2253void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2254{
2255 m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2256}
2257
2258KMime::Message::Ptr ComposerViewBase::msg() const
2259{
2260 return m_msg;
2261}
2262
2263std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
2264{
2265 if (!mExpiryChecker) {
2266 mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
2270 }
2271 return mExpiryChecker;
2272}
2273
2274#include "moc_composerviewbase.cpp"
Akonadi::Collection currentCollection() const
void setDefaultCollection(const Collection &collection)
bool isValid() const
void setMimeType(const QString &mimeType)
Id id() const
bool isValid() const
void setPayload(const T &p)
static SpecialMailCollections * self()
Akonadi::Collection defaultCollection(Type type) const
const Identity & identityForUoidOrDefault(uint uoid) const
KIdentityManagementCore::Identity::Id currentIdentity() const
virtual QString errorString() const
int error() const
void result(KJob *job)
virtual Q_SCRIPTABLE void start()=0
const Headers::ContentType * contentType() const
QByteArray decodedContent() const
const Headers::ContentID * contentID() const
static QList< Mailbox > listFromUnicodeString(const QString &s)
bool setCurrentTransport(int transportId)
Transport * transportById(Transport::Id id, bool def=true) const
static TransportManager * self()
void addAttachment(const MessageCore::AttachmentPart::Ptr &part)
sets sign, encrypt, shows properties dialog if so configured
The AttachmentModel class.
void addAttachment(const QUrl &url, const QString &comment, bool sync)
Add the given attachment to the message.
void setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts=false)
The following are various settings the user can modify when composing a message.
void setFrom(const QString &from)
Widgets for editing differ in client classes, so values are set before sending.
QString to() const
Header fields in recipients editor.
void send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher=true)
Send the message with the specified method, saving it in the specified folder.
void setMessage(const KMime::Message::Ptr &newMsg, bool allowDecryption)
Set the message to be opened in the composer window, and set the internal data structures to keep tra...
void setAttachmentModel(MessageComposer::AttachmentModel *model)
The following are for setting the various options and widgets in the composer.
bool isComposing() const
Returns true if there is at least one composer job running.
void failed(const QString &errorMessage, MessageComposer::ComposerViewBase::FailedType type=Sending)
Message sending failed with given error message.
void sentSuccessfully(Akonadi::Item::Id id)
Message sending completed successfully.
void updateAutoSave()
Enables/disables autosaving depending on the value of the autosave interval.
void cleanupAutoSave()
Stop autosaving and delete the autosaved message.
ComposerViewBase::MissingAttachment checkForMissingAttachments(const QStringList &attachmentKeywords)
Check if the mail has references to attachments, but no attachments are added to it.
void disableHtml(MessageComposer::ComposerViewBase::Confirmation)
Enabling or disabling HTML in the editor is affected by various client options, so when that would ot...
void setAutoSaveFileName(const QString &fileName)
Sets the filename to use when autosaving something.
void modified(bool isModified)
The composer was modified.
void autoSaveMessage()
Save the message.
The Composer class.
Definition composer.h:35
A job to resolve nicknames, distribution lists and email addresses for queued emails.
The GlobalPart class.
Definition globalpart.h:20
The InfoPart class contains the message header.
Definition infopart.h:22
QStringList cc
Carbon copy: The email address and optionally the name of the secondary recipients.
Definition infopart.h:32
QString from
The email address and optionally the name of the author of the mail.
Definition infopart.h:26
QStringList to
The email address and optionally the name of the primary recipients.
Definition infopart.h:29
QString fcc
The name of a file, to which a copy of the sent message should be appended.
Definition infopart.h:45
QStringList bcc
Blind Carbon copy: The email address and optionally the name of the secondary recipients.
Definition infopart.h:36
The RecipientsEditor class.
void removeRecipient(const QString &recipient, Recipient::Type type)
Removes the recipient provided it can be found and has the given type.
bool addRecipient(const QString &recipient, Recipient::Type type)
Adds a recipient (or multiple recipients) to one line of the editor.
The RichTextComposerNg class.
Send later information.
The SignatureController class Controls signature (the footer thing, not the crypto thing) operations ...
void applySignature(const KIdentityManagementCore::Signature &signature)
Adds the given signature to the editor, taking user preferences into account.
A class that encapsulates an attachment.
QSharedPointer< AttachmentPart > Ptr
Defines a pointer to an attachment object.
Parses messages and generates HTML display code out of them.
void add(const QString &entry)
static RecentAddresses * self(KConfig *config=nullptr)
QString currentDictionary() const
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
KCODECS_EXPORT QString normalizeAddressesAndEncodeIdn(const QString &str)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_MIME_EXPORT void copyMessageFlags(KMime::Message &from, Akonadi::Item &to)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
PostalAddress address(const QVariant &location)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
ButtonCode warningTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode warningTwoActionsCancel(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
KIOCORE_EXPORT QString dir(const QString &fileClass)
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
KGuiItem cont()
KGuiItem cancel()
const QList< QKeySequence > & end()
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
MESSAGECORE_EXPORT KMime::Content * nextSibling(const KMime::Content *node)
Returns the next sibling node of the given node.
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
Removes all private header fields (e.g.
const char * constData() const const
bool isEmpty() const const
bool isValid(int year, int month, int day)
QDateTime currentDateTime()
bool mkpath(const QString &dirPath) const const
int exec(ProcessEventsFlags flags)
void quit()
void append(QList< T > &&value)
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype removeAll(const AT &t)
void reserve(qsizetype size)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
T * data() const const
QString writableLocation(StandardLocation type)
void clear()
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QByteArray toLatin1() const const
qlonglong toLongLong(bool *ok, int base) const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
WaitCursor
void setHtml(const QString &text)
void setPlainText(const QString &text)
void start()
void stop()
void timeout()
QUrl fromLocalFile(const QString &localFile)
QUuid createUuid()
QString toString(StringFormat mode) const const
QVariant fromValue(T &&value)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:49:06 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.