Messagelib

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

KDE's Doxygen guidelines are available online.