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

KDE's Doxygen guidelines are available online.