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-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 <QDir>
76 #include <QStandardPaths>
77 #include <QTemporaryDir>
78 #include <QTimer>
79 #include <QUuid>
80 #include <followupreminder/followupremindercreatejob.h>
81 
82 using namespace MessageComposer;
83 
84 ComposerViewBase::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 
96 ComposerViewBase::~ComposerViewBase() = default;
97 
99 {
100  return !m_composers.isEmpty();
101 }
102 
103 void 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);
117  if (!resultTooManyRecipients) {
118  resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc);
119  }
120  if (!resultTooManyRecipients) {
121  resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc);
122  }
123  if (!resultTooManyRecipients) {
124  resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), MessageComposer::Recipient::ReplyTo);
125  }
126  m_recipientsEditor->setFocusBottom();
127 
128  if (!resultTooManyRecipients) {
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)) {
134  resultTooManyRecipients = true;
135  qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
136  break;
137  }
138  }
139  }
140  }
141  if (!resultTooManyRecipients) {
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.";
147  resultTooManyRecipients = true;
148  break;
149  }
150  }
151  }
152  }
153  if (!resultTooManyRecipients) {
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.";
159  resultTooManyRecipients = true;
160  break;
161  }
162  }
163  }
164  }
165  if (!resultTooManyRecipients) {
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.";
171  resultTooManyRecipients = true;
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.
182  auto msgContent = new KMime::Content;
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  delete msgContent;
230 }
231 
232 void ComposerViewBase::updateTemplate(const KMime::Message::Ptr &msg)
233 {
234  // First, we copy the message and then parse it to the object tree parser.
235  // The otp gets the message text out of it, in textualContent(), and also decrypts
236  // the message if necessary.
237  auto msgContent = new KMime::Content;
238  msgContent->setContent(msg->encodedContent());
239  msgContent->parse();
241  MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
242  otp.parseObjectTree(msgContent);
243  // Set the HTML text and collect HTML images
244  if (!otp.htmlContent().isEmpty()) {
245  m_editor->setHtml(otp.htmlContent());
246  Q_EMIT enableHtml();
247  collectImages(msg.data());
248  } else {
249  m_editor->setPlainText(otp.plainTextContent());
250  }
251 
252  if (auto hdr = msg->headerByType("X-KMail-CursorPos")) {
253  m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toInt());
254  }
255  delete msgContent;
256 }
257 
258 void ComposerViewBase::saveMailSettings()
259 {
260  const auto identity = currentIdentity();
261  auto header = new KMime::Headers::Generic("X-KMail-Transport");
262  header->fromUnicodeString(QString::number(m_transport->currentTransportId()), "utf-8");
263  m_msg->setHeader(header);
264 
265  header = new KMime::Headers::Generic("X-KMail-Transport-Name");
266  header->fromUnicodeString(m_transport->currentText(), "utf-8");
267  m_msg->setHeader(header);
268 
269  header = new KMime::Headers::Generic("X-KMail-Fcc");
270  header->fromUnicodeString(QString::number(m_fccCollection.id()), "utf-8");
271  m_msg->setHeader(header);
272 
273  header = new KMime::Headers::Generic("X-KMail-Identity");
274  header->fromUnicodeString(QString::number(identity.uoid()), "utf-8");
275  m_msg->setHeader(header);
276 
277  header = new KMime::Headers::Generic("X-KMail-Identity-Name");
278  header->fromUnicodeString(identity.identityName(), "utf-8");
279  m_msg->setHeader(header);
280 
281  header = new KMime::Headers::Generic("X-KMail-Dictionary");
282  header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
283  m_msg->setHeader(header);
284 
285  // Save the quote prefix which is used for this message. Each message can have
286  // a different quote prefix, for example depending on the original sender.
287  if (m_editor->quotePrefixName().isEmpty()) {
288  m_msg->removeHeader("X-KMail-QuotePrefix");
289  } else {
290  header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
291  header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
292  m_msg->setHeader(header);
293  }
294 
295  if (m_editor->composerControler()->isFormattingUsed()) {
296  qCDebug(MESSAGECOMPOSER_LOG) << "HTML mode";
297  header = new KMime::Headers::Generic("X-KMail-Markup");
298  header->fromUnicodeString(QStringLiteral("true"), "utf-8");
299  m_msg->setHeader(header);
300  } else {
301  m_msg->removeHeader("X-KMail-Markup");
302  qCDebug(MESSAGECOMPOSER_LOG) << "Plain text";
303  }
304 }
305 
306 void ComposerViewBase::clearFollowUp()
307 {
308  mFollowUpDate = QDate();
309  mFollowUpCollection = Akonadi::Collection();
310 }
311 
312 void ComposerViewBase::send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher)
313 {
314  mSendMethod = method;
315  mSaveIn = saveIn;
316 
318  const auto identity = currentIdentity();
319 
320  if (identity.attachVcard() && m_attachmentController->attachOwnVcard()) {
321  const QString vcardFileName = identity.vCardFile();
322  if (!vcardFileName.isEmpty()) {
323  m_attachmentController->addAttachmentUrlSync(QUrl::fromLocalFile(vcardFileName));
324  }
325  }
326  saveMailSettings();
327 
328  if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
329  const QString keepBtnText =
330  m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
331  const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
332  int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
333  i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
334  "<p>do you want to delete your markup?</p></qt>"),
335  i18nc("@title:window", "Sign/Encrypt Message?"),
336  KGuiItem(yesBtnText),
337  KGuiItem(keepBtnText));
338  if (KMessageBox::Cancel == ret) {
339  return;
340  }
341  if (KMessageBox::ButtonCode::SecondaryAction == ret) {
342  m_encrypt = false;
343  m_sign = false;
344  } else {
345  Q_EMIT disableHtml(NoConfirmationNeeded);
346  }
347  }
348 
349  if (m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone) {
350  // we can't use the state of the mail itself, to remember the
351  // signing and encryption state, so let's add a header instead
352  DraftSignatureState(m_msg).setState(m_sign);
353  DraftEncryptionState(m_msg).setState(m_encrypt);
354  DraftCryptoMessageFormatState(m_msg).setState(m_cryptoMessageFormat);
355  } else {
356  removeDraftCryptoHeaders(m_msg);
357  }
358 
359  if (mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) {
360  if (!MessageComposer::Util::sendMailDispatcherIsOnline(m_parentWidget)) {
361  qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to set sendmaildispatcher online. Please verify it";
362  return;
363  }
364  }
365 
366  readyForSending();
367 }
368 
369 void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
370 {
371  m_customHeader = customHeader;
372 }
373 
374 void ComposerViewBase::readyForSending()
375 {
376  qCDebug(MESSAGECOMPOSER_LOG) << "Entering readyForSending";
377  if (!m_msg) {
378  qCDebug(MESSAGECOMPOSER_LOG) << "m_msg == 0!";
379  return;
380  }
381 
382  if (!m_composers.isEmpty()) {
383  // This may happen if e.g. the autosave timer calls applyChanges.
384  qCDebug(MESSAGECOMPOSER_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
385  return;
386  }
387 
388  // first, expand all addresses
389  auto job = new MessageComposer::EmailAddressResolveJob(this);
390  const auto identity = currentIdentity();
391  if (!identity.isNull()) {
392  job->setDefaultDomainName(identity.defaultDomainName());
393  }
394  job->setFrom(from());
395  job->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
396  job->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
397  job->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
398  job->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
399 
400  connect(job, &MessageComposer::EmailAddressResolveJob::result, this, &ComposerViewBase::slotEmailAddressResolved);
401  job->start();
402 }
403 
404 void ComposerViewBase::slotEmailAddressResolved(KJob *job)
405 {
406  if (job->error()) {
407  qCWarning(MESSAGECOMPOSER_LOG) << "An error occurred while resolving the email addresses:" << job->errorString();
408  // This error could be caused by a broken search infrastructure, so we ignore it for now
409  // to not block sending emails completely.
410  }
411 
412  bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled();
413 
414  const MessageComposer::EmailAddressResolveJob *resolveJob = qobject_cast<MessageComposer::EmailAddressResolveJob *>(job);
415  if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
416  mExpandedFrom = resolveJob->expandedFrom();
417  mExpandedTo = resolveJob->expandedTo();
418  mExpandedCc = resolveJob->expandedCc();
419  mExpandedBcc = resolveJob->expandedBcc();
420  mExpandedReplyTo = resolveJob->expandedReplyTo();
421  if (autoresizeImage) {
422  QStringList listEmails;
423  listEmails << mExpandedFrom;
424  listEmails << mExpandedTo;
425  listEmails << mExpandedCc;
426  listEmails << mExpandedBcc;
427  listEmails << mExpandedReplyTo;
428  MessageComposer::Utils resizeUtils;
429  autoresizeImage = resizeUtils.filterRecipients(listEmails);
430  }
431  } else { // saved to draft, so keep the old values, not very nice.
432  mExpandedFrom = from();
433  const auto recipients{m_recipientsEditor->recipients()};
434  for (const MessageComposer::Recipient::Ptr &r : recipients) {
435  switch (r->type()) {
436  case MessageComposer::Recipient::To:
437  mExpandedTo << r->email();
438  break;
439  case MessageComposer::Recipient::Cc:
440  mExpandedCc << r->email();
441  break;
442  case MessageComposer::Recipient::Bcc:
443  mExpandedBcc << r->email();
444  break;
445  case MessageComposer::Recipient::ReplyTo:
446  mExpandedReplyTo << r->email();
447  break;
448  case MessageComposer::Recipient::Undefined:
449  Q_ASSERT(!"Unknown recipient type!");
450  break;
451  }
452  }
453  QStringList unExpandedTo;
454  QStringList unExpandedCc;
455  QStringList unExpandedBcc;
456  QStringList unExpandedReplyTo;
457  const auto expandedToLst{resolveJob->expandedTo()};
458  for (const QString &exp : expandedToLst) {
459  if (!mExpandedTo.contains(exp)) { // this address was expanded, so save it explicitly
460  unExpandedTo << exp;
461  }
462  }
463  const auto expandedCcLst{resolveJob->expandedCc()};
464  for (const QString &exp : expandedCcLst) {
465  if (!mExpandedCc.contains(exp)) {
466  unExpandedCc << exp;
467  }
468  }
469  const auto expandedBCcLst{resolveJob->expandedBcc()};
470  for (const QString &exp : expandedBCcLst) {
471  if (!mExpandedBcc.contains(exp)) { // this address was expanded, so save it explicitly
472  unExpandedBcc << exp;
473  }
474  }
475  const auto expandedReplyLst{resolveJob->expandedReplyTo()};
476  for (const QString &exp : expandedReplyLst) {
477  if (!mExpandedReplyTo.contains(exp)) { // this address was expanded, so save it explicitly
478  unExpandedReplyTo << exp;
479  }
480  }
481  auto header = new KMime::Headers::Generic("X-KMail-UnExpanded-To");
482  header->from7BitString(unExpandedTo.join(QLatin1String(", ")).toLatin1());
483  m_msg->setHeader(header);
484  header = new KMime::Headers::Generic("X-KMail-UnExpanded-CC");
485  header->from7BitString(unExpandedCc.join(QLatin1String(", ")).toLatin1());
486  m_msg->setHeader(header);
487  header = new KMime::Headers::Generic("X-KMail-UnExpanded-BCC");
488  header->from7BitString(unExpandedBcc.join(QLatin1String(", ")).toLatin1());
489  m_msg->setHeader(header);
490  header = new KMime::Headers::Generic("X-KMail-UnExpanded-Reply-To");
491  header->from7BitString(unExpandedReplyTo.join(QLatin1String(", ")).toLatin1());
492  m_msg->setHeader(header);
493  autoresizeImage = false;
494  }
495 
496  Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
497  // checks for emptiness before calling it
498  // so just ensure it actually is empty
499  // and document it
500  // we first figure out if we need to create multiple messages with different crypto formats
501  // if so, we create a composer per format
502  // if we aren't signing or encrypting, this just returns a single empty message
503  if (m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone && !mSendLaterInfo) {
504  auto composer = new MessageComposer::Composer;
505  composer->setNoCrypto(true);
506  m_composers.append(composer);
507  } else {
508  bool wasCanceled = false;
509  m_composers = generateCryptoMessages(wasCanceled);
510  if (wasCanceled) {
511  return;
512  }
513  }
514 
515  if (m_composers.isEmpty()) {
516  Q_EMIT failed(i18n("It was not possible to create a message composer."));
517  return;
518  }
519 
520  if (autoresizeImage) {
521  if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) {
522  if (m_attachmentModel) {
523  MessageComposer::Utils resizeUtils;
524  if (resizeUtils.containsImage(m_attachmentModel->attachments())) {
525  const int rc = KMessageBox::warningTwoActions(m_parentWidget,
526  i18n("Do you want to resize images?"),
527  i18nc("@title:window", "Auto Resize Images"),
528  KGuiItem(i18nc("@action:button", "Auto Resize")),
529  KGuiItem(i18nc("@action:button", "Do Not Resize")));
530  if (rc == KMessageBox::ButtonCode::PrimaryAction) {
531  autoresizeImage = true;
532  } else {
533  autoresizeImage = false;
534  }
535  } else {
536  autoresizeImage = false;
537  }
538  }
539  }
540  }
541  // Compose each message and prepare it for queueing, sending, or storing
542 
543  // working copy in case composers instantly emit result
544  const auto composers = m_composers;
545  for (MessageComposer::Composer *composer : composers) {
546  fillComposer(composer, UseExpandedRecipients, autoresizeImage);
547  connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
548  composer->start();
549  qCDebug(MESSAGECOMPOSER_LOG) << "Started a composer for sending!";
550  }
551 }
552 
553 namespace
554 {
555 // helper methods for reading encryption settings
556 
557 inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
558 {
559  if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
560  return Kleo::chrono::days{-1};
561  }
562  const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
563  return Kleo::chrono::days{qMax(1, num)};
564 }
565 
566 inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
567 {
568  if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
569  return Kleo::chrono::days{-1};
570  }
571  const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
572  return Kleo::chrono::days{qMax(1, num)};
573 }
574 
575 inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
576 {
577  if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
578  return Kleo::chrono::days{-1};
579  }
580  const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
581  return Kleo::chrono::days{qMax(1, num)};
582 }
583 
584 inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
585 {
586  if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
587  return Kleo::chrono::days{-1};
588  }
589  const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
590  return Kleo::chrono::days{qMax(1, num)};
591 }
592 
593 inline bool showKeyApprovalDialog()
594 {
595  return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
596 }
597 
598 inline bool cryptoWarningUnsigned(const KIdentityManagementCore::Identity &identity)
599 {
600  if (identity.encryptionOverride()) {
601  return identity.warnNotSign();
602  }
603  return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned();
604 }
605 
606 inline bool cryptoWarningUnencrypted(const KIdentityManagementCore::Identity &identity)
607 {
608  if (identity.encryptionOverride()) {
609  return identity.warnNotEncrypt();
610  }
611  return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted();
612 }
613 } // nameless namespace
614 
615 bool ComposerViewBase::addKeysToContext(const QString &gnupgHome,
616  const QList<QPair<QStringList, std::vector<GpgME::Key>>> &data,
617  const std::map<QByteArray, QString> &autocryptMap)
618 {
619  bool needSpecialContext = false;
620 
621  for (const auto &p : data) {
622  for (const auto &k : p.second) {
623  const auto it = autocryptMap.find(k.primaryFingerprint());
624  if (it != autocryptMap.end()) {
625  needSpecialContext = true;
626  break;
627  }
628  }
629  if (needSpecialContext) {
630  break;
631  }
632  }
633 
634  if (!needSpecialContext) {
635  return false;
636  }
637  const QGpgME::Protocol *proto(QGpgME::openpgp());
638 
639  const auto storage = MessageCore::AutocryptStorage::self();
640  QEventLoop loop;
641  int runningJobs = 0;
642  for (const auto &p : data) {
643  for (const auto &k : p.second) {
644  const auto it = autocryptMap.find(k.primaryFingerprint());
645  if (it == autocryptMap.end()) {
646  qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "via Export/Import";
647  auto exportJob = proto->publicKeyExportJob(false);
648  connect(exportJob,
649  &QGpgME::ExportJob::result,
650  [&gnupgHome, &proto, &runningJobs, &loop, &k](const GpgME::Error &result,
651  const QByteArray &keyData,
652  const QString &auditLogAsHtml,
653  const GpgME::Error &auditLogError) {
654  Q_UNUSED(auditLogAsHtml);
655  Q_UNUSED(auditLogError);
656  if (result) {
657  qCWarning(MESSAGECOMPOSER_LOG) << "Failed to export " << k.primaryFingerprint() << result.asString();
658  --runningJobs;
659  if (runningJobs < 1) {
660  loop.quit();
661  }
662  }
663 
664  auto importJob = proto->importJob();
665  QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
666  importJob->exec(keyData);
667  importJob->deleteLater();
668  --runningJobs;
669  if (runningJobs < 1) {
670  loop.quit();
671  }
672  });
673  QStringList patterns;
674  patterns << QString::fromUtf8(k.primaryFingerprint());
675  runningJobs++;
676  exportJob->start(patterns);
677  exportJob->setExportFlags(GpgME::Context::ExportMinimal);
678  } else {
679  qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage";
680  const auto recipient = storage->getRecipient(it->second.toUtf8());
681  auto key = recipient->gpgKey();
682  auto keydata = recipient->gpgKeydata();
683  if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) {
684  qCDebug(MESSAGECOMPOSER_LOG) << "Using gossipkey";
685  keydata = recipient->gossipKeydata();
686  }
687  auto importJob = proto->importJob();
688  QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
689  const auto result = importJob->exec(keydata);
690  importJob->deleteLater();
691  }
692  }
693  }
694  loop.exec();
695  return true;
696 }
697 
698 void ComposerViewBase::setAkonadiLookupEnabled(bool akonadiLookupEnabled)
699 {
700  m_akonadiLookupEnabled = akonadiLookupEnabled;
701 }
702 
703 QList<MessageComposer::Composer *> ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
704 {
705  const auto id = currentIdentity();
706 
707  bool canceled = false;
708 
709  qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
710  connect(expiryChecker().get(),
711  &Kleo::ExpiryChecker::expiryMessage,
712  this,
713  [&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
714  if (!isNewMessage) {
715  return;
716  }
717 
718  if (canceled) {
719  return;
720  }
721  QString title;
722  QString dontAskAgainName;
723  if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
724  dontAskAgainName = QStringLiteral("own key expires soon warning");
725  } else {
726  dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
727  }
728  if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
729  title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
730  } else {
731  title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
732  }
733  if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
734  == KMessageBox::Cancel) {
735  canceled = true;
736  }
737  });
738 
740  new Kleo::KeyResolver(true, showKeyApprovalDialog(), id.pgpAutoEncrypt(), m_cryptoMessageFormat, expiryChecker()));
741 
742  keyResolver->setAutocryptEnabled(autocryptEnabled());
743  keyResolver->setAkonadiLookupEnabled(m_akonadiLookupEnabled);
744 
745  QStringList encryptToSelfKeys;
746  QStringList signKeys;
747 
748  bool signSomething = m_sign;
749  bool doSignCompletely = m_sign;
750  bool encryptSomething = m_encrypt;
751  bool doEncryptCompletely = m_encrypt;
752 
753  // Add encryptionkeys from id to keyResolver
754  if (!id.pgpEncryptionKey().isEmpty()) {
755  encryptToSelfKeys.push_back(QLatin1String(id.pgpEncryptionKey()));
756  }
757  if (!id.smimeEncryptionKey().isEmpty()) {
758  encryptToSelfKeys.push_back(QLatin1String(id.smimeEncryptionKey()));
759  }
760  if (canceled || keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
761  qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
762  return {};
763  }
764 
765  // Add signingkeys from id to keyResolver
766  if (!id.pgpSigningKey().isEmpty()) {
767  signKeys.push_back(QLatin1String(id.pgpSigningKey()));
768  }
769  if (!id.smimeSigningKey().isEmpty()) {
770  signKeys.push_back(QLatin1String(id.smimeSigningKey()));
771  }
772  if (canceled || keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
773  qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
774  return {};
775  }
776 
777  if (m_attachmentModel) {
778  const auto attachments = m_attachmentModel->attachments();
779  for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
780  if (attachment->isSigned()) {
781  signSomething = true;
782  } else {
783  doEncryptCompletely = false;
784  }
785  if (attachment->isEncrypted()) {
786  encryptSomething = true;
787  } else {
788  doSignCompletely = false;
789  }
790  }
791  }
792 
793  const QStringList recipients = mExpandedTo + mExpandedCc;
794  const QStringList bcc(mExpandedBcc);
795 
796  keyResolver->setPrimaryRecipients(recipients);
797  keyResolver->setSecondaryRecipients(bcc);
798 
799  bool result = true;
800  canceled = false;
801  signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
802  if (!result) {
803  // TODO handle failure
804  qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
805  if (!canceled) {
806  Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
807  } else {
808  Q_EMIT failed(QString());
809  }
810  wasCanceled = canceled;
811  return {};
812  }
813 
814  canceled = false;
815  encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
816  if (!result) {
817  // TODO handle failure
818  qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
819  if (!canceled) {
820  Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
821  } else {
822  Q_EMIT failed(QString());
823  }
824 
825  wasCanceled = canceled;
826  return {};
827  }
828 
830 
831  // No encryption or signing is needed
832  if (!signSomething && !encryptSomething) {
833  auto composer = new MessageComposer::Composer;
834  if (m_cryptoMessageFormat & Kleo::OpenPGPMIMEFormat) {
835  composer->setAutocryptEnabled(autocryptEnabled());
836  if (keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat).size() > 0) {
837  composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]);
838  }
839  }
840  composers.append(composer);
841  return composers;
842  }
843 
844  canceled = false;
845  const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
846  if (kpgpResult == Kleo::Canceled || canceled) {
847  qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
848  return {};
849  } else if (kpgpResult != Kleo::Ok) {
850  // TODO handle failure
851  qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
852  Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
853  return {};
854  }
855 
856  qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys.";
857 
858  if (encryptSomething || signSomething) {
859  Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
860  for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
861  concreteFormat = concreteCryptoMessageFormats[i];
862  const auto encData = keyResolver->encryptionItems(concreteFormat);
863  if (encData.empty()) {
864  continue;
865  }
866 
867  if (!(concreteFormat & m_cryptoMessageFormat)) {
868  continue;
869  }
870 
871  auto composer = new MessageComposer::Composer;
872 
873  if (encryptSomething || autocryptEnabled()) {
874  auto end(encData.end());
876  data.reserve(encData.size());
877  for (auto it = encData.begin(); it != end; ++it) {
878  QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
879  data.append(p);
880  qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
881  }
882  composer->setEncryptionKeys(data);
883  if (concreteFormat & Kleo::OpenPGPMIMEFormat && autocryptEnabled()) {
884  composer->setAutocryptEnabled(autocryptEnabled());
885  composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
887  bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
888  if (specialGnupgHome) {
889  dir.setAutoRemove(false);
890  composer->setGnupgHome(dir.path());
891  }
892  }
893  }
894 
895  if (signSomething) {
896  // find signing keys for this format
897  std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
898  composer->setSigningKeys(signingKeys);
899  }
900 
901  composer->setMessageCryptoFormat(concreteFormat);
902  composer->setSignAndEncrypt(signSomething, encryptSomething);
903 
904  composers.append(composer);
905  }
906  } else {
907  auto composer = new MessageComposer::Composer;
908  composers.append(composer);
909  // If we canceled sign or encrypt be sure to change status in attachment.
910  markAllAttachmentsForSigning(false);
911  markAllAttachmentsForEncryption(false);
912  }
913 
914  if (composers.isEmpty() && (signSomething || encryptSomething)) {
915  Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
916  }
917 
918  return composers;
919 }
920 
921 void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
922 {
923  globalPart->setParentWidgetForGui(m_parentWidget);
924  globalPart->setCharsets(m_charsets);
925  globalPart->setMDNRequested(m_mdnRequested);
926  globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
927 }
928 
929 void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
930 {
931  // TODO splitAddressList and expandAliases ugliness should be handled by a
932  // special AddressListEdit widget... (later: see RecipientsEditor)
933 
934  if (m_fccCombo) {
935  infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
936  } else {
937  if (m_fccCollection.isValid()) {
938  infoPart->setFcc(QString::number(m_fccCollection.id()));
939  }
940  }
941 
942  infoPart->setTransportId(m_transport->currentTransportId());
943  if (expansion == UseExpandedRecipients) {
944  infoPart->setFrom(mExpandedFrom);
945  infoPart->setTo(mExpandedTo);
946  infoPart->setCc(mExpandedCc);
947  infoPart->setBcc(mExpandedBcc);
948  infoPart->setReplyTo(mExpandedReplyTo);
949  } else {
950  infoPart->setFrom(from());
951  infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
952  infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
953  infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
954  infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
955  }
956  infoPart->setSubject(subject());
957  infoPart->setUserAgent(QStringLiteral("KMail"));
958  infoPart->setUrgent(m_urgent);
959 
960  if (m_msg->inReplyTo()) {
961  infoPart->setInReplyTo(m_msg->inReplyTo()->asUnicodeString());
962  }
963 
964  if (m_msg->references()) {
965  infoPart->setReferences(m_msg->references()->asUnicodeString());
966  }
967 
969  if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
970  extras << hdr;
971  }
972  if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
973  extras << hdr;
974  }
975  if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
976  extras << hdr;
977  }
978  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
979  extras << hdr;
980  }
981  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
982  extras << hdr;
983  }
984  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
985  extras << hdr;
986  }
987  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
988  extras << hdr;
989  }
990  if (auto hdr = m_msg->organization(false)) {
991  extras << hdr;
992  }
993  if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
994  extras << hdr;
995  }
996  if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
997  extras << hdr;
998  }
999  if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
1000  extras << hdr;
1001  }
1002  if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
1003  extras << hdr;
1004  }
1005  if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
1006  extras << hdr;
1007  }
1008  if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
1009  extras << hdr;
1010  }
1011  if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
1012  extras << hdr;
1013  }
1014  if (auto hdr = m_msg->headerByType("X-Face")) {
1015  extras << hdr;
1016  }
1017  if (auto hdr = m_msg->headerByType("Face")) {
1018  extras << hdr;
1019  }
1020  if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
1021  extras << hdr;
1022  }
1023  if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
1024  extras << hdr;
1025  }
1026  if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1027  extras << hdr;
1028  }
1029 
1030  infoPart->setExtraHeaders(extras);
1031 }
1032 
1033 void ComposerViewBase::slotSendComposeResult(KJob *job)
1034 {
1035  Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1036  auto composer = static_cast<MessageComposer::Composer *>(job);
1037  if (composer->error() != MessageComposer::Composer::NoError) {
1038  qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1039  }
1040 
1041  if (composer->error() == MessageComposer::Composer::NoError) {
1042  Q_ASSERT(m_composers.contains(composer));
1043  // The messages were composed successfully.
1044  qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1045  const int numberOfMessage(composer->resultMessages().size());
1046  for (int i = 0; i < numberOfMessage; ++i) {
1047  if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1048  queueMessage(composer->resultMessages().at(i), composer);
1049  } else {
1050  saveMessage(composer->resultMessages().at(i), mSaveIn);
1051  }
1052  }
1053  saveRecentAddresses(composer->resultMessages().at(0));
1054  } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1055  // The job warned the user about something, and the user chose to return
1056  // to the message. Nothing to do.
1057  qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1058  Q_EMIT failed(i18n("Job cancelled by the user"));
1059  } else {
1060  qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1061  QString msg;
1062  if (composer->error() == MessageComposer::Composer::BugError) {
1063  msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1064  } else {
1065  msg = i18n("Could not compose message: %1", job->errorString());
1066  }
1067  Q_EMIT failed(msg);
1068  }
1069 
1070  if (!composer->gnupgHome().isEmpty()) {
1071  QDir dir(composer->gnupgHome());
1072  dir.removeRecursively();
1073  }
1074 
1075  m_composers.removeAll(composer);
1076 }
1077 
1078 void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1079 {
1080  KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1081  const QList<QByteArray> toAddresses = msg->to()->addresses();
1082  for (const QByteArray &address : toAddresses) {
1084  }
1085  const QList<QByteArray> ccAddresses = msg->cc()->addresses();
1086  for (const QByteArray &address : ccAddresses) {
1088  }
1089  const QList<QByteArray> bccAddresses = msg->bcc()->addresses();
1090  for (const QByteArray &address : bccAddresses) {
1092  }
1093 }
1094 
1095 void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
1096 {
1097  const MessageComposer::InfoPart *infoPart = composer->infoPart();
1098  auto qjob = new Akonadi::MessageQueueJob(this);
1099  qjob->setMessage(message);
1100  qjob->transportAttribute().setTransportId(infoPart->transportId());
1101  if (mSendMethod == MessageComposer::MessageSender::SendLater) {
1102  qjob->dispatchModeAttribute().setDispatchMode(Akonadi::DispatchModeAttribute::Manual);
1103  }
1104 
1105  if (message->hasHeader("X-KMail-FccDisabled")) {
1106  qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete);
1107  } else if (!infoPart->fcc().isEmpty()) {
1108  qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
1109 
1110  const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
1111  qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
1112  } else {
1113  qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToDefaultSentCollection);
1114  }
1115 
1116  MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
1117  if (transport && transport->specifySenderOverwriteAddress()) {
1118  qjob->addressAttribute().setFrom(
1120  } else {
1121  qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
1122  }
1123  // if this header is not empty, it contains the real recipient of the message, either the primary or one of the
1124  // secondary recipients. so we set that to the transport job, while leaving the message itself alone.
1125  if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
1126  qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(realTo->asUnicodeString().split(QLatin1Char('%'))));
1127  message->removeHeader("X-KMail-EncBccRecipients");
1128  message->assemble();
1129  qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
1130  } else {
1131  qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->to()));
1132  qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->cc()));
1133  qjob->addressAttribute().setBcc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->bcc()));
1134  }
1135  if (m_requestDeleveryConfirmation) {
1136  qjob->addressAttribute().setDeliveryStatusNotification(true);
1137  }
1138  MessageComposer::Util::addSendReplyForwardAction(message, qjob);
1140 
1141  MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1142  message->assemble();
1143  connect(qjob, &Akonadi::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
1144  m_pendingQueueJobs++;
1145  qjob->start();
1146 
1147  qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
1148 }
1149 
1150 void ComposerViewBase::slotQueueResult(KJob *job)
1151 {
1152  m_pendingQueueJobs--;
1153  auto qjob = static_cast<Akonadi::MessageQueueJob *>(job);
1154  qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
1155  Q_ASSERT(m_pendingQueueJobs >= 0);
1156 
1157  if (job->error()) {
1158  qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
1159  // There is not much we can do now, since all the MessageQueueJobs have been
1160  // started. So just wait for them to finish.
1161  // TODO show a message box or something
1162  QString msg = i18n("There were problems trying to queue the message for sending: %1", job->errorString());
1163 
1164  if (m_pendingQueueJobs == 0) {
1165  Q_EMIT failed(msg);
1166  return;
1167  }
1168  }
1169 
1170  if (m_pendingQueueJobs == 0) {
1171  addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
1173  }
1174 }
1175 
1176 void 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 
1192  updateAutoSave();
1193 }
1194 
1195 Akonadi::Collection ComposerViewBase::followUpCollection() const
1196 {
1197  return mFollowUpCollection;
1198 }
1199 
1200 void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1201 {
1202  mFollowUpCollection = followUpCollection;
1203 }
1204 
1205 QDate ComposerViewBase::followUpDate() const
1206 {
1207  return mFollowUpDate;
1208 }
1209 
1210 void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1211 {
1212  mFollowUpDate = followUpDate;
1213 }
1214 
1215 Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1216 {
1217  return m_dictionary;
1218 }
1219 
1220 void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1221 {
1222  m_dictionary = dictionary;
1223 }
1224 
1226 {
1227  if (m_autoSaveInterval == 0) {
1228  delete m_autoSaveTimer;
1229  m_autoSaveTimer = nullptr;
1230  } else {
1231  if (!m_autoSaveTimer) {
1232  m_autoSaveTimer = new QTimer(this);
1233  if (m_parentWidget) {
1234  connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
1235  } else {
1236  connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
1237  }
1238  }
1239  m_autoSaveTimer->start(m_autoSaveInterval);
1240  }
1241 }
1242 
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 + QLatin1String("*")};
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 
1300 void 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) {
1317  updateAutoSave();
1318  }
1319  } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1320  // The job warned the user about something, and the user chose to return
1321  // to the message. Nothing to do.
1322  qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1323  Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
1324  } else {
1325  qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
1326  Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
1327  }
1328 
1329  m_composers.removeAll(composer);
1330 }
1331 
1332 void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1333 {
1335  QDir().mkpath(autosavePath);
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 
1378 void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
1379 {
1380  Akonadi::Collection target;
1381  const auto identity = currentIdentity();
1382  message->date()->setDateTime(QDateTime::currentDateTime());
1383  if (!identity.isNull()) {
1384  if (auto header = message->headerByType("X-KMail-Fcc")) {
1385  const int sentCollectionId = header->asUnicodeString().toInt();
1386  if (identity.fcc() == QString::number(sentCollectionId)) {
1387  message->removeHeader("X-KMail-Fcc");
1388  }
1389  }
1390  }
1391  MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1392 
1393  message->assemble();
1394 
1395  Akonadi::Item item;
1396  item.setMimeType(QStringLiteral("message/rfc822"));
1397  item.setPayload(message);
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 
1419  auto saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
1420  saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
1421  QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
1422  } else {
1423  // preinitialize with the default collections
1424  target = defaultSpecialTarget();
1425  auto create = new Akonadi::ItemCreateJob(item, target, this);
1426  connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1427  ++m_pendingQueueJobs;
1428  }
1429 }
1430 
1431 void ComposerViewBase::slotSaveMessage(KJob *job)
1432 {
1433  Akonadi::Collection target;
1434  auto item = job->property("Akonadi::Item").value<Akonadi::Item>();
1435  if (job->error()) {
1436  target = defaultSpecialTarget();
1437  } else {
1438  const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
1439  if (fetchJob->collections().isEmpty()) {
1440  target = defaultSpecialTarget();
1441  } else {
1442  target = fetchJob->collections().at(0);
1443  }
1444  }
1445  auto create = new Akonadi::ItemCreateJob(item, target, this);
1446  connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1447  ++m_pendingQueueJobs;
1448 }
1449 
1450 Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1451 {
1452  Akonadi::Collection target;
1453  switch (mSaveIn) {
1454  case MessageComposer::MessageSender::SaveInNone:
1455  break;
1456  case MessageComposer::MessageSender::SaveInDrafts:
1458  break;
1459  case MessageComposer::MessageSender::SaveInTemplates:
1461  break;
1462  case MessageComposer::MessageSender::SaveInOutbox:
1464  break;
1465  }
1466 
1467  return target;
1468 }
1469 
1470 void ComposerViewBase::slotCreateItemResult(KJob *job)
1471 {
1472  --m_pendingQueueJobs;
1473  qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
1474  Q_ASSERT(m_pendingQueueJobs >= 0);
1475 
1476  if (job->error()) {
1477  qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
1478  Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
1479  return;
1480  }
1481 
1482  Akonadi::Item::Id id = -1;
1483  if (mSendLaterInfo) {
1484  auto createJob = static_cast<Akonadi::ItemCreateJob *>(job);
1485  const Akonadi::Item item = createJob->item();
1486  if (item.isValid()) {
1487  id = item.id();
1488  addSendLaterItem(item);
1489  }
1490  }
1491 
1492  if (m_pendingQueueJobs == 0) {
1494  }
1495 }
1496 
1497 void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
1498 {
1499  Q_UNUSED(comment)
1500  qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
1501  if (sync) {
1502  m_attachmentController->addAttachmentUrlSync(url);
1503  } else {
1504  m_attachmentController->addAttachment(url);
1505  }
1506 }
1507 
1508 void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1509 {
1511  if (!data.isEmpty()) {
1512  attachment->setName(name);
1513  attachment->setFileName(filename);
1514  attachment->setData(data);
1515  attachment->setCharset(charset.toLatin1());
1516  attachment->setMimeType(mimeType);
1517  // TODO what about the other fields?
1518 
1519  m_attachmentController->addAttachment(attachment);
1520  }
1521 }
1522 
1523 void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1524 {
1526  if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
1527  // if it is a digest or a full message, use the encodedContent() of the attachment,
1528  // which already has the proper headers
1529  part->setData(partToAttach->encodedContent());
1530  } else {
1531  part->setData(partToAttach->decodedContent());
1532  }
1533  part->setMimeType(partToAttach->contentType(false)->mimeType());
1534  if (auto cd = partToAttach->contentDescription(false)) {
1535  part->setDescription(cd->asUnicodeString());
1536  }
1537  if (auto ct = partToAttach->contentType(false)) {
1538  if (ct->hasParameter(QStringLiteral("name"))) {
1539  part->setName(ct->parameter(QStringLiteral("name")));
1540  }
1541  }
1542  if (auto cd = partToAttach->contentDisposition(false)) {
1543  part->setFileName(cd->filename());
1544  part->setInline(cd->disposition() == KMime::Headers::CDinline);
1545  }
1546  if (part->name().isEmpty() && !part->fileName().isEmpty()) {
1547  part->setName(part->fileName());
1548  }
1549  if (part->fileName().isEmpty() && !part->name().isEmpty()) {
1550  part->setFileName(part->name());
1551  }
1552  m_attachmentController->addAttachment(part);
1553 }
1554 
1555 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1556 {
1557  fillComposer(composer, UseUnExpandedRecipients, false);
1558 }
1559 
1560 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
1561 {
1562  fillGlobalPart(composer->globalPart());
1563  m_editor->fillComposerTextPart(composer->textPart());
1564  fillInfoPart(composer->infoPart(), expansion);
1565  if (m_attachmentModel) {
1566  composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
1567  }
1568 }
1569 
1570 //-----------------------------------------------------------------------------
1572 {
1573  if (m_recipientsEditor) {
1574  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1575  }
1576  return {};
1577 }
1578 
1579 //-----------------------------------------------------------------------------
1580 QString ComposerViewBase::cc() const
1581 {
1582  if (m_recipientsEditor) {
1583  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
1584  }
1585  return {};
1586 }
1587 
1588 //-----------------------------------------------------------------------------
1589 QString ComposerViewBase::bcc() const
1590 {
1591  if (m_recipientsEditor) {
1592  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1593  }
1594  return {};
1595 }
1596 
1597 QString ComposerViewBase::from() const
1598 {
1599  return MessageComposer::Util::cleanedUpHeaderString(m_from);
1600 }
1601 
1602 QString ComposerViewBase::replyTo() const
1603 {
1604  if (m_recipientsEditor) {
1605  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1606  }
1607  return {};
1608 }
1609 
1610 QString ComposerViewBase::subject() const
1611 {
1612  return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1613 }
1614 
1615 const KIdentityManagementCore::Identity &ComposerViewBase::currentIdentity() const
1616 {
1617  return m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1618 }
1619 
1620 bool ComposerViewBase::autocryptEnabled() const
1621 {
1622  return currentIdentity().autocryptEnabled();
1623 }
1624 
1625 void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1626 {
1627  m_parentWidget = w;
1628 }
1629 
1630 void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1631 {
1632  m_attachmentController = controller;
1633 }
1634 
1635 MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1636 {
1637  return m_attachmentController;
1638 }
1639 
1641 {
1642  m_attachmentModel = model;
1643 }
1644 
1645 MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1646 {
1647  return m_attachmentModel;
1648 }
1649 
1650 void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1651 {
1652  m_recipientsEditor = recEditor;
1653 }
1654 
1655 MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1656 {
1657  return m_recipientsEditor;
1658 }
1659 
1660 void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1661 {
1662  m_signatureController = sigController;
1663 }
1664 
1665 MessageComposer::SignatureController *ComposerViewBase::signatureController()
1666 {
1667  return m_signatureController;
1668 }
1669 
1670 void ComposerViewBase::setIdentityCombo(KIdentityManagementWidgets::IdentityCombo *identCombo)
1671 {
1672  m_identityCombo = identCombo;
1673 }
1674 
1675 KIdentityManagementWidgets::IdentityCombo *ComposerViewBase::identityCombo()
1676 {
1677  return m_identityCombo;
1678 }
1679 
1680 void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident,
1681  const KIdentityManagementCore::Identity &oldIdent,
1682  MessageComposer::Recipient::Type type)
1683 {
1684  QString oldIdentList;
1685  QString newIdentList;
1686  if (type == MessageComposer::Recipient::Bcc) {
1687  oldIdentList = oldIdent.bcc();
1688  newIdentList = ident.bcc();
1689  } else if (type == MessageComposer::Recipient::Cc) {
1690  oldIdentList = oldIdent.cc();
1691  newIdentList = ident.cc();
1692  } else if (type == MessageComposer::Recipient::ReplyTo) {
1693  oldIdentList = oldIdent.replyToAddr();
1694  newIdentList = ident.replyToAddr();
1695  } else {
1696  return;
1697  }
1698 
1699  if (oldIdentList != newIdentList) {
1700  const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
1701  for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1702  m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1703  }
1704 
1705  const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
1706  for (const KMime::Types::Mailbox &recipient : newRecipients) {
1707  m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1708  }
1709  m_recipientsEditor->setFocusBottom();
1710  }
1711 }
1712 
1713 void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
1714 {
1715  updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1716  updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1717  updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1718 
1719  KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
1720  KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
1721  // replace existing signatures
1722  const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1723  // Just append the signature if there was no old signature
1724  if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1725  signatureController()->applySignature(newSig);
1726  }
1727  const QString vcardFileName = ident.vCardFile();
1728  attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1729  attachmentController()->setAttachOwnVcard(ident.attachVcard());
1730 
1731  m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1732 }
1733 
1734 void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1735 {
1736  m_editor = editor;
1737  m_editor->document()->setModified(false);
1738 }
1739 
1740 MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1741 {
1742  return m_editor;
1743 }
1744 
1745 void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1746 {
1747  m_transport = transpCombo;
1748 }
1749 
1750 MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1751 {
1752  return m_transport;
1753 }
1754 
1755 void ComposerViewBase::setIdentityManager(KIdentityManagementCore::IdentityManager *identMan)
1756 {
1757  m_identMan = identMan;
1758 }
1759 
1760 KIdentityManagementCore::IdentityManager *ComposerViewBase::identityManager()
1761 {
1762  return m_identMan;
1763 }
1764 
1765 void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1766 {
1767  if (m_fccCombo) {
1768  m_fccCombo->setDefaultCollection(fccCollection);
1769  } else {
1770  m_fccCollection = fccCollection;
1771  }
1772  auto const checkFccCollectionJob = new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
1773  connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1774 }
1775 
1776 void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1777 {
1778  if (job->error()) {
1779  qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1781  if (m_fccCombo) {
1782  m_fccCombo->setDefaultCollection(sentMailCol);
1783  } else {
1784  m_fccCollection = sentMailCol;
1785  }
1786  }
1787 }
1788 
1789 void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1790 {
1791  m_fccCombo = fcc;
1792 }
1793 
1794 Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1795 {
1796  return m_fccCombo;
1797 }
1798 
1800 {
1801  m_from = from;
1802 }
1803 
1804 void ComposerViewBase::setSubject(const QString &subject)
1805 {
1806  m_subject = subject;
1807  if (mSendLaterInfo) {
1808  mSendLaterInfo->setSubject(m_subject);
1809  mSendLaterInfo->setTo(to());
1810  }
1811 }
1812 
1813 void ComposerViewBase::setAutoSaveInterval(int interval)
1814 {
1815  m_autoSaveInterval = interval;
1816 }
1817 
1818 void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1819 {
1820  m_sign = sign;
1821  m_encrypt = encrypt;
1822  m_cryptoMessageFormat = format;
1823  m_neverEncrypt = neverEncryptDrafts;
1824 }
1825 
1826 void ComposerViewBase::setCharsets(const QList<QByteArray> &charsets)
1827 {
1828  m_charsets = charsets;
1829 }
1830 
1831 void ComposerViewBase::setMDNRequested(bool mdnRequested)
1832 {
1833  m_mdnRequested = mdnRequested;
1834 }
1835 
1836 void ComposerViewBase::setUrgent(bool urgent)
1837 {
1838  m_urgent = urgent;
1839 }
1840 
1841 int ComposerViewBase::autoSaveInterval() const
1842 {
1843  return m_autoSaveInterval;
1844 }
1845 
1846 //-----------------------------------------------------------------------------
1847 void ComposerViewBase::collectImages(KMime::Content *root)
1848 {
1849  if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1850  KMime::Content *parentnode = n->parent();
1851  if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1853  while (node) {
1854  if (node->contentType()->isImage()) {
1855  qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1856  QImage img;
1857  img.loadFromData(node->decodedContent());
1858  m_editor->composerControler()->composerImages()->loadImage(
1859  img,
1860  QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1861  node->contentType()->name());
1862  }
1864  }
1865  }
1866  }
1867 }
1868 
1869 //-----------------------------------------------------------------------------
1870 bool ComposerViewBase::inlineSigningEncryptionSelected() const
1871 {
1872  if (!m_sign && !m_encrypt) {
1873  return false;
1874  }
1875  return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1876 }
1877 
1878 bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1879 {
1880  if (attachmentKeywords.isEmpty()) {
1881  return false;
1882  }
1883  if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1884  return false;
1885  }
1886 
1887  return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1888 }
1889 
1890 ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1891 {
1892  if (!hasMissingAttachments(attachmentKeywords)) {
1893  return NoMissingAttachmentFound;
1894  }
1895  const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
1896 
1897  i18n("The message you have composed seems to refer to an "
1898  "attached file but you have not attached anything.\n"
1899  "Do you want to attach a file to your message?"),
1900  i18nc("@title:window", "File Attachment Reminder"),
1901  KGuiItem(i18n("&Attach File..."), QLatin1String("mail-attachment")),
1902  KGuiItem(i18n("&Send as Is"), QLatin1String("mail-send")));
1903  if (rc == KMessageBox::Cancel) {
1904  return FoundMissingAttachmentAndCancel;
1905  }
1906  if (rc == KMessageBox::ButtonCode::PrimaryAction) {
1907  m_attachmentController->showAddAttachmentFileDialog();
1908  return FoundMissingAttachmentAndAddedAttachment;
1909  }
1910 
1911  return FoundMissingAttachmentAndSending;
1912 }
1913 
1914 void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1915 {
1916  if (m_attachmentModel) {
1917  const auto attachments = m_attachmentModel->attachments();
1918  for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1919  attachment->setSigned(sign);
1920  }
1921  }
1922 }
1923 
1924 void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1925 {
1926  if (m_attachmentModel) {
1927  const auto attachments = m_attachmentModel->attachments();
1928  for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1929  attachment->setEncrypted(encrypt);
1930  }
1931  }
1932 }
1933 
1934 bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1935 {
1936  bool sign = false;
1937  switch (keyResolver->checkSigningPreferences(signSomething)) {
1938  case Kleo::DoIt:
1939  if (!signSomething) {
1940  markAllAttachmentsForSigning(true);
1941  return true;
1942  }
1943  sign = true;
1944  break;
1945  case Kleo::DontDoIt:
1946  sign = false;
1947  break;
1948  case Kleo::AskOpportunistic:
1949  assert(0);
1950  case Kleo::Ask: {
1951  // the user wants to be asked or has to be asked
1953  const QString msg = i18n(
1954  "Examination of the recipient's signing preferences "
1955  "yielded that you be asked whether or not to sign "
1956  "this message.\n"
1957  "Sign this message?");
1958  switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1959  msg,
1960  i18nc("@title:window", "Sign Message?"),
1961  KGuiItem(i18nc("to sign", "&Sign")),
1962  KGuiItem(i18n("Do &Not Sign")))) {
1963  case KMessageBox::Cancel:
1964  result = false;
1965  canceled = true;
1966  return false;
1967  case KMessageBox::ButtonCode::PrimaryAction:
1968  markAllAttachmentsForSigning(true);
1969  return true;
1970  case KMessageBox::ButtonCode::SecondaryAction:
1971  markAllAttachmentsForSigning(false);
1972  return false;
1973  default:
1974  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1975  return false;
1976  }
1977  break;
1978  }
1979  case Kleo::Conflict: {
1980  // warn the user that there are conflicting signing preferences
1982  const QString msg = i18n(
1983  "There are conflicting signing preferences "
1984  "for these recipients.\n"
1985  "Sign this message?");
1986  switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1987  msg,
1988  i18nc("@title:window", "Sign Message?"),
1989  KGuiItem(i18nc("to sign", "&Sign")),
1990  KGuiItem(i18n("Do &Not Sign")))) {
1991  case KMessageBox::Cancel:
1992  result = false;
1993  canceled = true;
1994  return false;
1995  case KMessageBox::ButtonCode::PrimaryAction:
1996  markAllAttachmentsForSigning(true);
1997  return true;
1998  case KMessageBox::ButtonCode::SecondaryAction:
1999  markAllAttachmentsForSigning(false);
2000  return false;
2001  default:
2002  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2003  return false;
2004  }
2005  break;
2006  }
2007  case Kleo::Impossible: {
2009  const QString msg = i18n(
2010  "You have requested to sign this message, "
2011  "but no valid signing keys have been configured "
2012  "for this identity.");
2013  if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18nc("@title:window", "Send Unsigned?"), KGuiItem(i18n("Send &Unsigned")))
2014  == KMessageBox::Cancel) {
2015  result = false;
2016  return false;
2017  } else {
2018  markAllAttachmentsForSigning(false);
2019  return false;
2020  }
2021  }
2022  }
2023 
2024  if (!sign || !doSignCompletely) {
2025  if (cryptoWarningUnsigned(currentIdentity())) {
2027  const QString msg = sign && !doSignCompletely ? i18n(
2028  "Some parts of this message will not be signed.\n"
2029  "Sending only partially signed messages might violate site policy.\n"
2030  "Sign all parts instead?") // oh, I hate this...
2031  : i18n(
2032  "This message will not be signed.\n"
2033  "Sending unsigned message might violate site policy.\n"
2034  "Sign message instead?"); // oh, I hate this...
2035  const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
2036  switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2037  msg,
2038  i18nc("@title:window", "Unsigned-Message Warning"),
2039  KGuiItem(buttonText),
2040  KGuiItem(i18n("Send &As Is")))) {
2041  case KMessageBox::Cancel:
2042  result = false;
2043  canceled = true;
2044  return false;
2045  case KMessageBox::ButtonCode::PrimaryAction:
2046  markAllAttachmentsForSigning(true);
2047  return true;
2048  case KMessageBox::ButtonCode::SecondaryAction:
2049  return sign || doSignCompletely;
2050  default:
2051  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2052  return false;
2053  }
2054  }
2055  }
2056  return sign || doSignCompletely;
2057 }
2058 
2059 bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2060  Kleo::KeyResolver *keyResolver,
2061  bool encryptSomething,
2062  bool signSomething,
2063  bool &result,
2064  bool &canceled)
2065 {
2066  bool encrypt = false;
2067  bool opportunistic = false;
2068  switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2069  case Kleo::DoIt:
2070  if (!encryptSomething) {
2071  markAllAttachmentsForEncryption(true);
2072  return true;
2073  }
2074  encrypt = true;
2075  break;
2076  case Kleo::DontDoIt:
2077  encrypt = false;
2078  break;
2079  case Kleo::AskOpportunistic:
2080  opportunistic = true;
2081  // fall through...
2082  Q_FALLTHROUGH();
2083  case Kleo::Ask: {
2084  // the user wants to be asked or has to be asked
2086  const QString msg = opportunistic ? i18n(
2087  "Valid trusted encryption keys were found for all recipients.\n"
2088  "Encrypt this message?")
2089  : i18n(
2090  "Examination of the recipient's encryption preferences "
2091  "yielded that you be asked whether or not to encrypt "
2092  "this message.\n"
2093  "Encrypt this message?");
2094  switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2095  msg,
2096  i18n("Encrypt Message?"),
2097  KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2098  KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2099  case KMessageBox::Cancel:
2100  result = false;
2101  canceled = true;
2102  return false;
2103  case KMessageBox::ButtonCode::PrimaryAction:
2104  markAllAttachmentsForEncryption(true);
2105  return true;
2106  case KMessageBox::ButtonCode::SecondaryAction:
2107  markAllAttachmentsForEncryption(false);
2108  return false;
2109  default:
2110  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2111  return false;
2112  }
2113  break;
2114  }
2115  case Kleo::Conflict: {
2116  // warn the user that there are conflicting encryption preferences
2118  const QString msg = i18n(
2119  "There are conflicting encryption preferences "
2120  "for these recipients.\n"
2121  "Encrypt this message?");
2123 
2124  m_parentWidget,
2125  msg,
2126  i18n("Encrypt Message?"),
2127  KGuiItem(i18n("&Encrypt")),
2128  KGuiItem(i18n("Do &Not Encrypt")))) {
2129  case KMessageBox::Cancel:
2130  result = false;
2131  canceled = true;
2132  return false;
2133  case KMessageBox::ButtonCode::PrimaryAction:
2134  markAllAttachmentsForEncryption(true);
2135  return true;
2136  case KMessageBox::ButtonCode::SecondaryAction:
2137  markAllAttachmentsForEncryption(false);
2138  return false;
2139  default:
2140  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2141  return false;
2142  }
2143  break;
2144  }
2145  case Kleo::Impossible: {
2147  const QString msg = i18n(
2148  "You have requested to encrypt this message, "
2149  "and to encrypt a copy to yourself, "
2150  "but no valid trusted encryption keys have been "
2151  "configured for this identity.");
2152  if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18nc("@title:window", "Send Unencrypted?"), KGuiItem(i18n("Send &Unencrypted")))
2153  == KMessageBox::Cancel) {
2154  result = false;
2155  return false;
2156  } else {
2157  markAllAttachmentsForEncryption(false);
2158  return false;
2159  }
2160  }
2161  }
2162 
2163  if (!encrypt || !doEncryptCompletely) {
2164  if (cryptoWarningUnencrypted(currentIdentity())) {
2166  const QString msg = !doEncryptCompletely ? i18n(
2167  "Some parts of this message will not be encrypted.\n"
2168  "Sending only partially encrypted messages might violate "
2169  "site policy and/or leak sensitive information.\n"
2170  "Encrypt all parts instead?") // oh, I hate this...
2171  : i18n(
2172  "This message will not be encrypted.\n"
2173  "Sending unencrypted messages might violate site policy and/or "
2174  "leak sensitive information.\n"
2175  "Encrypt messages instead?"); // oh, I hate this...
2176  const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2177  switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2178  msg,
2179  i18nc("@title:window", "Unencrypted Message Warning"),
2180  KGuiItem(buttonText),
2181  KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2182  case KMessageBox::Cancel:
2183  result = false;
2184  canceled = true;
2185  return false;
2186  case KMessageBox::ButtonCode::PrimaryAction:
2187  markAllAttachmentsForEncryption(true);
2188  return true;
2189  case KMessageBox::ButtonCode::SecondaryAction:
2190  return encrypt || doEncryptCompletely;
2191  default:
2192  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2193  return false;
2194  }
2195  }
2196  }
2197 
2198  return encrypt || doEncryptCompletely;
2199 }
2200 
2201 void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2202 {
2203  mSendLaterInfo.reset(info);
2204 }
2205 
2206 SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2207 {
2208  return mSendLaterInfo.get();
2209 }
2210 
2211 void ComposerViewBase::addFollowupReminder(const QString &messageId)
2212 {
2213  if (!messageId.isEmpty()) {
2214  if (mFollowUpDate.isValid()) {
2216  job->setSubject(m_subject);
2217  job->setMessageId(messageId);
2218  job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2219  job->setFollowUpReminderDate(mFollowUpDate);
2220  job->setCollectionToDo(mFollowUpCollection);
2221  job->start();
2222  }
2223  }
2224 }
2225 
2226 void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2227 {
2228  mSendLaterInfo->setItemId(item.id());
2229 
2230  auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2231  job->start();
2232 }
2233 
2234 bool ComposerViewBase::requestDeleveryConfirmation() const
2235 {
2236  return m_requestDeleveryConfirmation;
2237 }
2238 
2239 void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2240 {
2241  m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2242 }
2243 
2244 KMime::Message::Ptr ComposerViewBase::msg() const
2245 {
2246  return m_msg;
2247 }
2248 
2249 std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
2250 {
2251  if (!mExpiryChecker) {
2252  mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
2253  encryptKeyNearExpiryWarningThresholdInDays(),
2254  encryptRootCertNearExpiryWarningThresholdInDays(),
2255  encryptChainCertNearExpiryWarningThresholdInDays()}});
2256  }
2257  return mExpiryChecker;
2258 }
2259 
2260 #include "moc_composerviewbase.cpp"
bool isValid() const
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
void append(const T &value)
QStringList expandedBcc() const
Returns the expanded Bcc field.
Send later information.
Definition: sendlaterinfo.h:17
T * data() const const
QString expandedFrom() const
Returns the expanded From field.
void disableHtml(MessageComposer::ComposerViewBase::Confirmation)
Enabling or disabling HTML in the editor is affected by various client options, so when that would ot...
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
Akonadi::Collection defaultCollection(Type type) const
QString number(int n, int base)
void autoSaveMessage()
Save the message.
QString fcc
The name of a file, to which a copy of the sent message should be appended.
Definition: infopart.h:45
QString fromUtf8(const char *str, int size)
int size() const const
virtual Q_SCRIPTABLE void start()=0
QVariant fromValue(const T &value)
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)
bool setCurrentTransport(int transportId)
QByteArray mimeType() const
QStringList to
The email address and optionally the name of the primary recipients.
Definition: infopart.h:29
Q_EMITQ_EMIT
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
Removes all private header fields (e.g.
Definition: stringutil.cpp:354
Content * parent() const
void result(KJob *job)
int removeAll(const T &value)
The RecipientsEditor class.
MESSAGECORE_EXPORT KMime::Content * nextSibling(const KMime::Content *node)
Returns the next sibling node of the given node.
QDateTime currentDateTime()
A class that encapsulates an attachment.
int count(const T &value) const const
T value() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void clear()
void addAttachment(const QUrl &url, const QString &comment, bool sync)
Add the given attachment to the message.
void setAutoSaveFileName(const QString &fileName)
Sets the filename to use when autosaving something.
qlonglong toLongLong(bool *ok, int base) const const
void sentSuccessfully(Akonadi::Item::Id id)
Message sending completed successfully.
The SignatureController class Controls signature (the footer thing, not the crypto thing) operations ...
The Composer class.
Definition: composer.h:34
const Identity & identityForUoidOrDefault(uint uoid) const
void setMimeType(const QString &mimeType)
Parses messages and generates HTML display code out of them.
void push_back(const T &value)
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...
QString writableLocation(QStandardPaths::StandardLocation type)
QStringList expandedCc() const
Returns the expanded CC field.
QByteArray toLatin1() const const
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 applySignature(const KIdentityManagementCore::Signature &signature)
Adds the given signature to the editor, taking user preferences into account.
int exec(QEventLoop::ProcessEventsFlags flags)
bool contains(const T &value) const const
Headers::ContentID * contentID(bool create=true)
bool loadFromData(const uchar *data, int len, const char *format)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
AKONADI_MIME_EXPORT void copyMessageFlags(KMime::Message &from, Akonadi::Item &to)
QString autocorrectionLanguage() const
The GlobalPart class.
Definition: globalpart.h:19
KCharsets * charsets()
void reserve(int alloc)
KGuiItem cancel()
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
Collection::List collections() const
void setDefaultCollection(const Collection &collection)
void modified(bool isModified)
The composer was modified.
void start(int msec)
void setFrom(const QString &from)
Widgets for editing differ in client classes, so values are set before sending.
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
QString i18n(const char *text, const TYPE &arg...)
QSharedPointer< AttachmentPart > Ptr
Defines a pointer to an attachment object.
void quit()
A job to resolve nicknames, distribution lists and email addresses for queued emails.
QByteArray encodedContent(bool useCrLf=false)
QStringList cc
Carbon copy: The email address and optionally the name of the secondary recipients.
Definition: infopart.h:32
QString currentDictionary() const
void cleanupAutoSave()
Stop autosaving and delete the autosaved message.
void timeout()
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
WaitCursor
QByteArray toUtf8() const const
const T & at(int i) const const
bool mkpath(const QString &dirPath) const const
QUuid createUuid()
void removeRecipient(const QString &recipient, Recipient::Type type)
Removes the recipient provided it can be found and has the given type.
Headers::ContentDisposition * contentDisposition(bool create=true)
The FollowupReminderCreateJob class.
void setAttachmentModel(MessageComposer::AttachmentModel *model)
The following are for setting the various options and widgets in the composer.
static TransportManager * self()
bool isComposing() const
Returns true if there is at least one composer job running.
void setNameFilters(const QStringList &nameFilters)
int toInt(bool *ok, int base) const const
bool isValid() const const
QStringList expandedReplyTo() const
Returns the expanded Reply-To field.
bool isEmpty() const const
void setAutoSave(bool isAutoSave)
Sets if this message being composed is an auto-saved message if so, might need different handling,...
Definition: composer.cpp:673
The InfoPart class contains the message header.
Definition: infopart.h:21
QByteArray subType() const
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 setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts=false)
The following are various settings the user can modify when composing a message.
QString htmlContent() const
Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part.
bool remove(const QString &fileName)
bool addRecipient(const QString &recipient, Recipient::Type type)
Adds a recipient (or multiple recipients) to one line of the editor.
QString join(const QString &separator) const const
void addAttachment(const MessageCore::AttachmentPart::Ptr &part)
sets sign, encrypt, shows properties dialog if so configured
KSharedConfigPtr config()
The RichTextComposerNg class.
QString toString() const const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString & replace(int position, int n, QChar after)
QString plainTextContent() const
The text of the message, ie.
void parseObjectTree(KMime::Content *node, bool parseOnlySingleNode=false)
Parse beginning at a given node and recursively parsing the children of that node and it's next sibli...
QByteArray decodedContent()
QString rawText(bool *ok=nullptr, QString *errorMessage=nullptr) const
void setContent(const QByteArray &s)
The AttachmentControllerBase class.
bool isEmpty() const const
Id id() const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
QString from
The email address and optionally the name of the author of the mail.
Definition: infopart.h:26
Akonadi::Collection currentCollection() const
void add(const QString &entry)
void updateAutoSave()
Enables/disables autosaving depending on the value of the autosave interval.
const char * constData() const const
void stop()
QString fromLatin1(const char *str, int size)
QList< KMime::Content * > attachmentsOfExtraContents() const
Returns a list of attachments of attached extra content nodes.
QStringList expandedTo() const
Returns the expanded To field.
KGuiItem cont()
static SpecialMailCollections * self()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
The AttachmentModel class.
QString to() const
Header fields in recipients editor.
bool isValid() const
void failed(const QString &errorMessage, MessageComposer::ComposerViewBase::FailedType type=Sending)
Message sending failed with given error message.
void setPayload(const T &p)
ComposerViewBase::MissingAttachment checkForMissingAttachments(const QStringList &attachmentKeywords)
Check if the mail has references to attachments, but no attachments are added to it.
Headers::ContentType * contentType(bool create=true)
virtual QString errorString() const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
int error() const
Action checkSigningPreferences(bool signingRequested) const
Determine whether to sign or not, depending on the per-recipient signing preferences,...
QStringList bcc
Blind Carbon copy: The email address and optionally the name of the secondary recipients.
Definition: infopart.h:36
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))
Transport * transportById(Transport::Id id, bool def=true) const
QString message
static QList< Mailbox > listFromUnicodeString(const QString &s)
const QList< QKeySequence > & end()
Action checkEncryptionPreferences(bool encryptionRequested) const
Determine whether to encrypt or not, depending on the per-recipient encryption preferences,...
Headers::ContentDescription * contentDescription(bool create=true)
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
QVariant property(const char *name) const const
KCODECS_EXPORT QString normalizeAddressesAndEncodeIdn(const QString &str)
static RecentAddresses * self(KConfig *config=nullptr)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Oct 1 2023 03:53:34 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.