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

KDE's Doxygen guidelines are available online.