Messagelib

composerviewbase.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, [email protected]
3  SPDX-FileCopyrightText: 2010 Leo Franchi <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "composerviewbase.h"
9 
10 #include "attachment/attachmentcontrollerbase.h"
11 #include "attachment/attachmentmodel.h"
12 #include "composer-ng/richtextcomposerng.h"
13 #include "composer-ng/richtextcomposersignatures.h"
14 #include "composer.h"
15 #include "composer/keyresolver.h"
16 #include "composer/signaturecontroller.h"
17 #include "draftstatus/draftstatus.h"
18 #include "imagescaling/imagescalingutils.h"
19 #include "job/emailaddressresolvejob.h"
20 #include "part/globalpart.h"
21 #include "part/infopart.h"
22 #include "utils/kleo_util.h"
23 #include "utils/util.h"
24 #include "utils/util_p.h"
25 #include <KPIMTextEdit/RichTextComposerControler>
26 #include <KPIMTextEdit/RichTextComposerImages>
27 
28 #include "sendlater/sendlatercreatejob.h"
29 #include "sendlater/sendlaterinfo.h"
30 
31 #include "helper/messagehelper.h"
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/KMime/MessageFlags>
55 #include <Akonadi/KMime/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++/gpgmepp_version.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 
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);
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 #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0
684  exportJob->setExportFlags(GpgME::Context::ExportMinimal);
685 #endif
686  } else {
687  qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage";
688  const auto recipient = storage->getRecipient(it->second.toUtf8());
689  auto key = recipient->gpgKey();
690  auto keydata = recipient->gpgKeydata();
691  if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) {
692  qCDebug(MESSAGECOMPOSER_LOG) << "Using gossipkey";
693  keydata = recipient->gossipKeydata();
694  }
695  auto importJob = proto->importJob();
696  QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
697  const auto result = importJob->exec(keydata);
698  importJob->deleteLater();
699  }
700  }
701  }
702  loop.exec();
703  return true;
704 }
705 
706 void ComposerViewBase::setAkonadiLookupEnabled(bool akonadiLookupEnabled)
707 {
708  m_akonadiLookupEnabled = akonadiLookupEnabled;
709 }
710 
711 QVector<MessageComposer::Composer *> ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
712 {
713  const auto id = currentIdentity();
714 
715  qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
716  QScopedPointer<Kleo::KeyResolver> keyResolver(new Kleo::KeyResolver(encryptToSelf(),
717  showKeyApprovalDialog(),
718  id.pgpAutoEncrypt(),
719  m_cryptoMessageFormat,
720  encryptKeyNearExpiryWarningThresholdInDays(),
721  signingKeyNearExpiryWarningThresholdInDays(),
722  encryptRootCertNearExpiryWarningThresholdInDays(),
723  signingRootCertNearExpiryWarningThresholdInDays(),
724  encryptChainCertNearExpiryWarningThresholdInDays(),
725  signingChainCertNearExpiryWarningThresholdInDays()));
726 
727  keyResolver->setAutocryptEnabled(autocryptEnabled());
728  keyResolver->setAkonadiLookupEnabled(m_akonadiLookupEnabled);
729 
730  QStringList encryptToSelfKeys;
731  QStringList signKeys;
732 
733  bool signSomething = m_sign;
734  bool doSignCompletely = m_sign;
735  bool encryptSomething = m_encrypt;
736  bool doEncryptCompletely = m_encrypt;
737 
738  // Add encryptionkeys from id to keyResolver
739  if (!id.pgpEncryptionKey().isEmpty()) {
740  encryptToSelfKeys.push_back(QLatin1String(id.pgpEncryptionKey()));
741  }
742  if (!id.smimeEncryptionKey().isEmpty()) {
743  encryptToSelfKeys.push_back(QLatin1String(id.smimeEncryptionKey()));
744  }
745  if (keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
746  qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
747  return {};
748  }
749 
750  // Add signingkeys from id to keyResolver
751  if (!id.pgpSigningKey().isEmpty()) {
752  signKeys.push_back(QLatin1String(id.pgpSigningKey()));
753  }
754  if (!id.smimeSigningKey().isEmpty()) {
755  signKeys.push_back(QLatin1String(id.smimeSigningKey()));
756  }
757  if (keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
758  qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
759  return {};
760  }
761 
762  if (m_attachmentModel) {
763  const auto attachments = m_attachmentModel->attachments();
764  for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
765  if (attachment->isSigned()) {
766  signSomething = true;
767  } else {
768  doEncryptCompletely = false;
769  }
770  if (attachment->isEncrypted()) {
771  encryptSomething = true;
772  } else {
773  doSignCompletely = false;
774  }
775  }
776  }
777 
778  const QStringList recipients = mExpandedTo + mExpandedCc;
779  const QStringList bcc(mExpandedBcc);
780 
781  keyResolver->setPrimaryRecipients(recipients);
782  keyResolver->setSecondaryRecipients(bcc);
783 
784  bool result = true;
785  bool canceled = false;
786  signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
787  if (!result) {
788  // TODO handle failure
789  qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
790  if (!canceled) {
791  Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
792  } else {
793  Q_EMIT failed(QString());
794  }
795  wasCanceled = canceled;
796  return {};
797  }
798 
799  canceled = false;
800  encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
801  if (!result) {
802  // TODO handle failure
803  qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
804  if (!canceled) {
805  Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
806  } else {
807  Q_EMIT failed(QString());
808  }
809 
810  wasCanceled = canceled;
811  return {};
812  }
813 
815 
816  // No encryption or signing is needed
817  if (!signSomething && !encryptSomething) {
818  auto composer = new MessageComposer::Composer;
819  if (m_cryptoMessageFormat & Kleo::OpenPGPMIMEFormat) {
820  composer->setAutocryptEnabled(autocryptEnabled());
821  composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]);
822  }
823  composers.append(composer);
824  return composers;
825  }
826 
827  const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
828  if (kpgpResult == Kleo::Canceled) {
829  qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
830  return {};
831  } else if (kpgpResult != Kleo::Ok) {
832  // TODO handle failure
833  qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
834  Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
835  return {};
836  }
837 
838  qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys.";
839 
840  if (encryptSomething || signSomething) {
841  Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
842  for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
843  concreteFormat = concreteCryptoMessageFormats[i];
844  const auto encData = keyResolver->encryptionItems(concreteFormat);
845  if (encData.empty()) {
846  continue;
847  }
848 
849  if (!(concreteFormat & m_cryptoMessageFormat)) {
850  continue;
851  }
852 
853  auto composer = new MessageComposer::Composer;
854 
855  if (encryptSomething || autocryptEnabled()) {
856  auto end(encData.end());
858  data.reserve(encData.size());
859  for (auto it = encData.begin(); it != end; ++it) {
860  QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
861  data.append(p);
862  qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
863  }
864  composer->setEncryptionKeys(data);
865  if (concreteFormat & Kleo::OpenPGPMIMEFormat && autocryptEnabled()) {
866  composer->setAutocryptEnabled(autocryptEnabled());
867  composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
868  QTemporaryDir dir;
869  bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
870  if (specialGnupgHome) {
871  dir.setAutoRemove(false);
872  composer->setGnupgHome(dir.path());
873  }
874  }
875  }
876 
877  if (signSomething) {
878  // find signing keys for this format
879  std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
880  composer->setSigningKeys(signingKeys);
881  }
882 
883  composer->setMessageCryptoFormat(concreteFormat);
884  composer->setSignAndEncrypt(signSomething, encryptSomething);
885 
886  composers.append(composer);
887  }
888  } else {
889  auto composer = new MessageComposer::Composer;
890  composers.append(composer);
891  // If we canceled sign or encrypt be sure to change status in attachment.
892  markAllAttachmentsForSigning(false);
893  markAllAttachmentsForEncryption(false);
894  }
895 
896  if (composers.isEmpty() && (signSomething || encryptSomething)) {
897  Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
898  }
899 
900  return composers;
901 }
902 
903 void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
904 {
905  globalPart->setParentWidgetForGui(m_parentWidget);
906  globalPart->setCharsets(m_charsets);
907  globalPart->setMDNRequested(m_mdnRequested);
908  globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
909 }
910 
911 void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
912 {
913  // TODO splitAddressList and expandAliases ugliness should be handled by a
914  // special AddressListEdit widget... (later: see RecipientsEditor)
915 
916  if (m_fccCombo) {
917  infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
918  } else {
919  if (m_fccCollection.isValid()) {
920  infoPart->setFcc(QString::number(m_fccCollection.id()));
921  }
922  }
923 
924  infoPart->setTransportId(m_transport->currentTransportId());
925  if (expansion == UseExpandedRecipients) {
926  infoPart->setFrom(mExpandedFrom);
927  infoPart->setTo(mExpandedTo);
928  infoPart->setCc(mExpandedCc);
929  infoPart->setBcc(mExpandedBcc);
930  infoPart->setReplyTo(mExpandedReplyTo);
931  } else {
932  infoPart->setFrom(from());
933  infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
934  infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
935  infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
936  infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
937  }
938  infoPart->setSubject(subject());
939  infoPart->setUserAgent(QStringLiteral("KMail"));
940  infoPart->setUrgent(m_urgent);
941 
942  if (m_msg->inReplyTo()) {
943  infoPart->setInReplyTo(m_msg->inReplyTo()->asUnicodeString());
944  }
945 
946  if (m_msg->references()) {
947  infoPart->setReferences(m_msg->references()->asUnicodeString());
948  }
949 
951  if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
952  extras << hdr;
953  }
954  if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
955  extras << hdr;
956  }
957  if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
958  extras << hdr;
959  }
960  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
961  extras << hdr;
962  }
963  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
964  extras << hdr;
965  }
966  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
967  extras << hdr;
968  }
969  if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
970  extras << hdr;
971  }
972  if (auto hdr = m_msg->organization(false)) {
973  extras << hdr;
974  }
975  if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
976  extras << hdr;
977  }
978  if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
979  extras << hdr;
980  }
981  if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
982  extras << hdr;
983  }
984  if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
985  extras << hdr;
986  }
987  if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
988  extras << hdr;
989  }
990  if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
991  extras << hdr;
992  }
993  if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
994  extras << hdr;
995  }
996  if (auto hdr = m_msg->headerByType("X-Face")) {
997  extras << hdr;
998  }
999  if (auto hdr = m_msg->headerByType("Face")) {
1000  extras << hdr;
1001  }
1002  if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
1003  extras << hdr;
1004  }
1005  if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
1006  extras << hdr;
1007  }
1008  if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1009  extras << hdr;
1010  }
1011 
1012  infoPart->setExtraHeaders(extras);
1013 }
1014 
1015 void ComposerViewBase::slotSendComposeResult(KJob *job)
1016 {
1017  Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1018  auto composer = static_cast<MessageComposer::Composer *>(job);
1019  if (composer->error() != MessageComposer::Composer::NoError) {
1020  qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1021  }
1022 
1023  if (composer->error() == MessageComposer::Composer::NoError) {
1024  Q_ASSERT(m_composers.contains(composer));
1025  // The messages were composed successfully.
1026  qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1027  const int numberOfMessage(composer->resultMessages().size());
1028  for (int i = 0; i < numberOfMessage; ++i) {
1029  if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1030  queueMessage(composer->resultMessages().at(i), composer);
1031  } else {
1032  saveMessage(composer->resultMessages().at(i), mSaveIn);
1033  }
1034  }
1035  saveRecentAddresses(composer->resultMessages().at(0));
1036  } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1037  // The job warned the user about something, and the user chose to return
1038  // to the message. Nothing to do.
1039  qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1040  Q_EMIT failed(i18n("Job cancelled by the user"));
1041  } else {
1042  qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1043  QString msg;
1044  if (composer->error() == MessageComposer::Composer::BugError) {
1045  msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1046  } else {
1047  msg = i18n("Could not compose message: %1", job->errorString());
1048  }
1049  Q_EMIT failed(msg);
1050  }
1051 
1052  if (!composer->gnupgHome().isEmpty()) {
1053  QDir dir(composer->gnupgHome());
1054  dir.removeRecursively();
1055  }
1056 
1057  m_composers.removeAll(composer);
1058 }
1059 
1060 void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1061 {
1062  KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1063  const QVector<QByteArray> toAddresses = msg->to()->addresses();
1064  for (const QByteArray &address : toAddresses) {
1066  }
1067  const QVector<QByteArray> ccAddresses = msg->cc()->addresses();
1068  for (const QByteArray &address : ccAddresses) {
1070  }
1071  const QVector<QByteArray> bccAddresses = msg->bcc()->addresses();
1072  for (const QByteArray &address : bccAddresses) {
1074  }
1075 }
1076 
1077 void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
1078 {
1079  const MessageComposer::InfoPart *infoPart = composer->infoPart();
1080  auto qjob = new MailTransport::MessageQueueJob(this);
1081  qjob->setMessage(message);
1082  qjob->transportAttribute().setTransportId(infoPart->transportId());
1083  if (mSendMethod == MessageComposer::MessageSender::SendLater) {
1084  qjob->dispatchModeAttribute().setDispatchMode(MailTransport::DispatchModeAttribute::Manual);
1085  }
1086 
1087  if (message->hasHeader("X-KMail-FccDisabled")) {
1088  qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::Delete);
1089  } else if (!infoPart->fcc().isEmpty()) {
1090  qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToCollection);
1091 
1092  const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
1093  qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
1094  } else {
1095  qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection);
1096  }
1097 
1098  MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
1099  if (transport && transport->specifySenderOverwriteAddress()) {
1100  qjob->addressAttribute().setFrom(
1102  } else {
1103  qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
1104  }
1105  // if this header is not empty, it contains the real recipient of the message, either the primary or one of the
1106  // secondary recipients. so we set that to the transport job, while leaving the message itself alone.
1107  if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
1108  qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(realTo->asUnicodeString().split(QLatin1Char('%'))));
1109  message->removeHeader("X-KMail-EncBccRecipients");
1110  message->assemble();
1111  qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
1112  } else {
1113  qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->to()));
1114  qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->cc()));
1115  qjob->addressAttribute().setBcc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->bcc()));
1116  }
1117  if (m_requestDeleveryConfirmation) {
1118  qjob->addressAttribute().setDeliveryStatusNotification(true);
1119  }
1120  MessageComposer::Util::addSendReplyForwardAction(message, qjob);
1122 
1123  MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1124  message->assemble();
1125  connect(qjob, &MailTransport::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
1126  m_pendingQueueJobs++;
1127  qjob->start();
1128 
1129  qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
1130 }
1131 
1132 void ComposerViewBase::slotQueueResult(KJob *job)
1133 {
1134  m_pendingQueueJobs--;
1135  auto qjob = static_cast<MailTransport::MessageQueueJob *>(job);
1136  qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
1137  Q_ASSERT(m_pendingQueueJobs >= 0);
1138 
1139  if (job->error()) {
1140  qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
1141  // There is not much we can do now, since all the MessageQueueJobs have been
1142  // started. So just wait for them to finish.
1143  // TODO show a message box or something
1144  QString msg = i18n("There were problems trying to queue the message for sending: %1", job->errorString());
1145 
1146  if (m_pendingQueueJobs == 0) {
1147  Q_EMIT failed(msg);
1148  return;
1149  }
1150  }
1151 
1152  if (m_pendingQueueJobs == 0) {
1153  addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
1154  Q_EMIT sentSuccessfully(-1);
1155  }
1156 }
1157 
1158 void ComposerViewBase::initAutoSave()
1159 {
1160  qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
1161 
1162  // Ensure that the autosave directory exists.
1164  if (!dataDirectory.exists(QStringLiteral("autosave"))) {
1165  qCDebug(MESSAGECOMPOSER_LOG) << "Creating autosave directory.";
1166  dataDirectory.mkdir(QStringLiteral("autosave"));
1167  }
1168 
1169  // Construct a file name
1170  if (m_autoSaveUUID.isEmpty()) {
1171  m_autoSaveUUID = QUuid::createUuid().toString();
1172  }
1173 
1174  updateAutoSave();
1175 }
1176 
1177 Akonadi::Collection ComposerViewBase::followUpCollection() const
1178 {
1179  return mFollowUpCollection;
1180 }
1181 
1182 void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1183 {
1184  mFollowUpCollection = followUpCollection;
1185 }
1186 
1187 QDate ComposerViewBase::followUpDate() const
1188 {
1189  return mFollowUpDate;
1190 }
1191 
1192 void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1193 {
1194  mFollowUpDate = followUpDate;
1195 }
1196 
1197 Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1198 {
1199  return m_dictionary;
1200 }
1201 
1202 void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1203 {
1204  m_dictionary = dictionary;
1205 }
1206 
1208 {
1209  if (m_autoSaveInterval == 0) {
1210  delete m_autoSaveTimer;
1211  m_autoSaveTimer = nullptr;
1212  } else {
1213  if (!m_autoSaveTimer) {
1214  m_autoSaveTimer = new QTimer(this);
1215  if (m_parentWidget) {
1216  connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
1217  } else {
1218  connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
1219  }
1220  }
1221  m_autoSaveTimer->start(m_autoSaveInterval);
1222  }
1223 }
1224 
1226 {
1227  delete m_autoSaveTimer;
1228  m_autoSaveTimer = nullptr;
1229  if (!m_autoSaveUUID.isEmpty()) {
1230  qCDebug(MESSAGECOMPOSER_LOG) << "deleting autosave files" << m_autoSaveUUID;
1231 
1232  // Delete the autosave files
1234 
1235  // Filter out only this composer window's autosave files
1236  const QStringList autoSaveFilter{m_autoSaveUUID + QLatin1String("*")};
1237  autoSaveDir.setNameFilters(autoSaveFilter);
1238 
1239  // Return the files to be removed
1240  const QStringList autoSaveFiles = autoSaveDir.entryList();
1241  qCDebug(MESSAGECOMPOSER_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
1242 
1243  // Delete each file
1244  for (const QString &file : autoSaveFiles) {
1245  autoSaveDir.remove(file);
1246  }
1247  m_autoSaveUUID.clear();
1248  }
1249 }
1250 
1251 //-----------------------------------------------------------------------------
1253 {
1254  qCDebug(MESSAGECOMPOSER_LOG) << "Autosaving message";
1255 
1256  if (m_autoSaveTimer) {
1257  m_autoSaveTimer->stop();
1258  }
1259 
1260  if (!m_composers.isEmpty()) {
1261  // This may happen if e.g. the autosave timer calls applyChanges.
1262  qCDebug(MESSAGECOMPOSER_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
1263  return;
1264  }
1265 
1266  auto composer = new Composer();
1267  fillComposer(composer);
1268  composer->setAutoSave(true);
1269  composer->setAutocryptEnabled(autocryptEnabled());
1270  m_composers.append(composer);
1271  connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
1272  composer->start();
1273 }
1274 
1276 {
1277  m_autoSaveUUID = fileName;
1278 
1279  Q_EMIT modified(true);
1280 }
1281 
1282 void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
1283 {
1285 
1286  Q_ASSERT(dynamic_cast<Composer *>(job));
1287  auto composer = static_cast<Composer *>(job);
1288 
1289  if (composer->error() == Composer::NoError) {
1290  Q_ASSERT(m_composers.contains(composer));
1291 
1292  // The messages were composed successfully. Only save the first message, there should
1293  // only be one anyway, since crypto is disabled.
1294  qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1295  writeAutoSaveToDisk(composer->resultMessages().constFirst());
1296  Q_ASSERT(composer->resultMessages().size() == 1);
1297 
1298  if (m_autoSaveInterval > 0) {
1299  updateAutoSave();
1300  }
1301  } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1302  // The job warned the user about something, and the user chose to return
1303  // to the message. Nothing to do.
1304  qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1305  Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
1306  } else {
1307  qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
1308  Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
1309  }
1310 
1311  m_composers.removeAll(composer);
1312 }
1313 
1314 void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1315 {
1317  QDir().mkpath(autosavePath);
1318  const QString filename = autosavePath + m_autoSaveUUID;
1319  QSaveFile file(filename);
1321  qCDebug(MESSAGECOMPOSER_LOG) << "Writing message to disk as" << filename;
1322 
1323  if (file.open(QIODevice::WriteOnly)) {
1325 
1326  if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
1327  errorMessage = i18n("Could not write all data to file.");
1328  } else {
1329  if (!file.commit()) {
1330  errorMessage = i18n("Could not finalize the file.");
1331  }
1332  }
1333  } else {
1334  errorMessage = i18n("Could not open file.");
1335  }
1336 
1337  if (!errorMessage.isEmpty()) {
1338  qCWarning(MESSAGECOMPOSER_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
1339  if (!m_autoSaveErrorShown) {
1340  KMessageBox::sorry(m_parentWidget,
1341  i18n("Autosaving the message as %1 failed.\n"
1342  "%2\n"
1343  "Reason: %3",
1344  filename,
1345  errorMessage,
1346  file.errorString()),
1347  i18n("Autosaving Message Failed"));
1348 
1349  // Error dialog shown, hide the errors the next time
1350  m_autoSaveErrorShown = true;
1351  }
1352  } else {
1353  // No error occurred, the next error should be shown again
1354  m_autoSaveErrorShown = false;
1355  }
1356  file.commit();
1357  message->clear();
1358 }
1359 
1360 void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
1361 {
1362  Akonadi::Collection target;
1363  const auto identity = currentIdentity();
1364  message->date()->setDateTime(QDateTime::currentDateTime());
1365  if (!identity.isNull()) {
1366  if (auto header = message->headerByType("X-KMail-Fcc")) {
1367  const int sentCollectionId = header->asUnicodeString().toInt();
1368  if (identity.fcc() == QString::number(sentCollectionId)) {
1369  message->removeHeader("X-KMail-Fcc");
1370  }
1371  }
1372  }
1373  MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1374 
1375  message->assemble();
1376 
1377  Akonadi::Item item;
1378  item.setMimeType(QStringLiteral("message/rfc822"));
1379  item.setPayload(message);
1381 
1382  if (!identity.isNull()) { // we have a valid identity
1383  switch (saveIn) {
1384  case MessageComposer::MessageSender::SaveInTemplates:
1385  if (!identity.templates().isEmpty()) { // the user has specified a custom templates collection
1386  target = Akonadi::Collection(identity.templates().toLongLong());
1387  }
1388  break;
1389  case MessageComposer::MessageSender::SaveInDrafts:
1390  if (!identity.drafts().isEmpty()) { // the user has specified a custom drafts collection
1391  target = Akonadi::Collection(identity.drafts().toLongLong());
1392  }
1393  break;
1394  case MessageComposer::MessageSender::SaveInOutbox: // We don't define save outbox in identity
1396  break;
1397  case MessageComposer::MessageSender::SaveInNone:
1398  break;
1399  }
1400 
1401  auto saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
1402  saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
1403  QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
1404  } else {
1405  // preinitialize with the default collections
1406  target = defaultSpecialTarget();
1407  auto create = new Akonadi::ItemCreateJob(item, target, this);
1408  connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1409  ++m_pendingQueueJobs;
1410  }
1411 }
1412 
1413 void ComposerViewBase::slotSaveMessage(KJob *job)
1414 {
1415  Akonadi::Collection target;
1416  auto item = job->property("Akonadi::Item").value<Akonadi::Item>();
1417  if (job->error()) {
1418  target = defaultSpecialTarget();
1419  } else {
1420  const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
1421  if (fetchJob->collections().isEmpty()) {
1422  target = defaultSpecialTarget();
1423  } else {
1424  target = fetchJob->collections().at(0);
1425  }
1426  }
1427  auto create = new Akonadi::ItemCreateJob(item, target, this);
1428  connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1429  ++m_pendingQueueJobs;
1430 }
1431 
1432 Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1433 {
1434  Akonadi::Collection target;
1435  switch (mSaveIn) {
1436  case MessageComposer::MessageSender::SaveInNone:
1437  break;
1438  case MessageComposer::MessageSender::SaveInDrafts:
1440  break;
1441  case MessageComposer::MessageSender::SaveInTemplates:
1443  break;
1444  case MessageComposer::MessageSender::SaveInOutbox:
1446  break;
1447  }
1448 
1449  return target;
1450 }
1451 
1452 void ComposerViewBase::slotCreateItemResult(KJob *job)
1453 {
1454  --m_pendingQueueJobs;
1455  qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
1456  Q_ASSERT(m_pendingQueueJobs >= 0);
1457 
1458  if (job->error()) {
1459  qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
1460  Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
1461  return;
1462  }
1463 
1464  Akonadi::Item::Id id = -1;
1465  if (mSendLaterInfo) {
1466  auto createJob = static_cast<Akonadi::ItemCreateJob *>(job);
1467  const Akonadi::Item item = createJob->item();
1468  if (item.isValid()) {
1469  id = item.id();
1470  addSendLaterItem(item);
1471  }
1472  }
1473 
1474  if (m_pendingQueueJobs == 0) {
1475  Q_EMIT sentSuccessfully(id);
1476  }
1477 }
1478 
1479 void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
1480 {
1481  Q_UNUSED(comment)
1482  qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
1483  if (sync) {
1484  m_attachmentController->addAttachmentUrlSync(url);
1485  } else {
1486  m_attachmentController->addAttachment(url);
1487  }
1488 }
1489 
1490 void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1491 {
1493  if (!data.isEmpty()) {
1494  attachment->setName(name);
1495  attachment->setFileName(filename);
1496  attachment->setData(data);
1497  attachment->setCharset(charset.toLatin1());
1498  attachment->setMimeType(mimeType);
1499  // TODO what about the other fields?
1500 
1501  m_attachmentController->addAttachment(attachment);
1502  }
1503 }
1504 
1505 void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1506 {
1508  if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
1509  // if it is a digest or a full message, use the encodedContent() of the attachment,
1510  // which already has the proper headers
1511  part->setData(partToAttach->encodedContent());
1512  } else {
1513  part->setData(partToAttach->decodedContent());
1514  }
1515  part->setMimeType(partToAttach->contentType(false)->mimeType());
1516  if (auto cd = partToAttach->contentDescription(false)) {
1517  part->setDescription(cd->asUnicodeString());
1518  }
1519  if (auto ct = partToAttach->contentType(false)) {
1520  if (ct->hasParameter(QStringLiteral("name"))) {
1521  part->setName(ct->parameter(QStringLiteral("name")));
1522  }
1523  }
1524  if (auto cd = partToAttach->contentDisposition(false)) {
1525  part->setFileName(cd->filename());
1526  part->setInline(cd->disposition() == KMime::Headers::CDinline);
1527  }
1528  if (part->name().isEmpty() && !part->fileName().isEmpty()) {
1529  part->setName(part->fileName());
1530  }
1531  if (part->fileName().isEmpty() && !part->name().isEmpty()) {
1532  part->setFileName(part->name());
1533  }
1534  m_attachmentController->addAttachment(part);
1535 }
1536 
1537 
1538 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1539 {
1540  fillComposer(composer, UseUnExpandedRecipients);
1541 }
1542 
1543 void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion)
1544 {
1545  fillGlobalPart(composer->globalPart());
1546  m_editor->fillComposerTextPart(composer->textPart());
1547  fillInfoPart(composer->infoPart(), expansion);
1548  if (m_attachmentModel) {
1549  composer->addAttachmentParts(m_attachmentModel->attachments());
1550  }
1551 }
1552 
1553 //-----------------------------------------------------------------------------
1555 {
1556  if (m_recipientsEditor) {
1557  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1558  }
1559  return {};
1560 }
1561 
1562 //-----------------------------------------------------------------------------
1563 QString ComposerViewBase::cc() const
1564 {
1565  if (m_recipientsEditor) {
1566  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
1567  }
1568  return {};
1569 }
1570 
1571 //-----------------------------------------------------------------------------
1572 QString ComposerViewBase::bcc() const
1573 {
1574  if (m_recipientsEditor) {
1575  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1576  }
1577  return {};
1578 }
1579 
1580 QString ComposerViewBase::from() const
1581 {
1582  return MessageComposer::Util::cleanedUpHeaderString(m_from);
1583 }
1584 
1585 QString ComposerViewBase::replyTo() const
1586 {
1587  if (m_recipientsEditor) {
1588  return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1589  }
1590  return {};
1591 }
1592 
1593 QString ComposerViewBase::subject() const
1594 {
1595  return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1596 }
1597 
1598 const KIdentityManagement::Identity &ComposerViewBase::currentIdentity() const
1599 {
1600  return m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1601 }
1602 
1603 bool ComposerViewBase::autocryptEnabled() const
1604 {
1605  return currentIdentity().autocryptEnabled();
1606 }
1607 
1608 void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1609 {
1610  m_parentWidget = w;
1611 }
1612 
1613 void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1614 {
1615  m_attachmentController = controller;
1616 }
1617 
1618 MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1619 {
1620  return m_attachmentController;
1621 }
1622 
1624 {
1625  m_attachmentModel = model;
1626 }
1627 
1628 MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1629 {
1630  return m_attachmentModel;
1631 }
1632 
1633 void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1634 {
1635  m_recipientsEditor = recEditor;
1636 }
1637 
1638 MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1639 {
1640  return m_recipientsEditor;
1641 }
1642 
1643 void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1644 {
1645  m_signatureController = sigController;
1646 }
1647 
1648 MessageComposer::SignatureController *ComposerViewBase::signatureController()
1649 {
1650  return m_signatureController;
1651 }
1652 
1653 void ComposerViewBase::setIdentityCombo(KIdentityManagement::IdentityCombo *identCombo)
1654 {
1655  m_identityCombo = identCombo;
1656 }
1657 
1658 KIdentityManagement::IdentityCombo *ComposerViewBase::identityCombo()
1659 {
1660  return m_identityCombo;
1661 }
1662 
1663 void ComposerViewBase::updateRecipients(const KIdentityManagement::Identity &ident,
1664  const KIdentityManagement::Identity &oldIdent,
1665  MessageComposer::Recipient::Type type)
1666 {
1667  QString oldIdentList;
1668  QString newIdentList;
1669  if (type == MessageComposer::Recipient::Bcc) {
1670  oldIdentList = oldIdent.bcc();
1671  newIdentList = ident.bcc();
1672  } else if (type == MessageComposer::Recipient::Cc) {
1673  oldIdentList = oldIdent.cc();
1674  newIdentList = ident.cc();
1675  } else if (type == MessageComposer::Recipient::ReplyTo) {
1676  oldIdentList = oldIdent.replyToAddr();
1677  newIdentList = ident.replyToAddr();
1678  } else {
1679  return;
1680  }
1681 
1682  if (oldIdentList != newIdentList) {
1683  const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
1684  for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1685  m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1686  }
1687 
1688  const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
1689  for (const KMime::Types::Mailbox &recipient : newRecipients) {
1690  m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1691  }
1692  m_recipientsEditor->setFocusBottom();
1693  }
1694 }
1695 
1696 void ComposerViewBase::identityChanged(const KIdentityManagement::Identity &ident, const KIdentityManagement::Identity &oldIdent, bool msgCleared)
1697 {
1698  updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1699  updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1700  updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1701 
1702  KIdentityManagement::Signature oldSig = const_cast<KIdentityManagement::Identity &>(oldIdent).signature();
1703  KIdentityManagement::Signature newSig = const_cast<KIdentityManagement::Identity &>(ident).signature();
1704  // replace existing signatures
1705  const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1706  // Just append the signature if there was no old signature
1707  if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1708  signatureController()->applySignature(newSig);
1709  }
1710  const QString vcardFileName = ident.vCardFile();
1711  attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1712  attachmentController()->setAttachOwnVcard(ident.attachVcard());
1713 
1714  m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1715 }
1716 
1717 void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1718 {
1719  m_editor = editor;
1720  m_editor->document()->setModified(false);
1721 }
1722 
1723 MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1724 {
1725  return m_editor;
1726 }
1727 
1728 void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1729 {
1730  m_transport = transpCombo;
1731 }
1732 
1733 MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1734 {
1735  return m_transport;
1736 }
1737 
1738 void ComposerViewBase::setIdentityManager(KIdentityManagement::IdentityManager *identMan)
1739 {
1740  m_identMan = identMan;
1741 }
1742 
1743 KIdentityManagement::IdentityManager *ComposerViewBase::identityManager()
1744 {
1745  return m_identMan;
1746 }
1747 
1748 void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1749 {
1750  if (m_fccCombo) {
1751  m_fccCombo->setDefaultCollection(fccCollection);
1752  } else {
1753  m_fccCollection = fccCollection;
1754  }
1755  auto const checkFccCollectionJob = new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
1756  connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1757 }
1758 
1759 void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1760 {
1761  if (job->error()) {
1762  qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1764  if (m_fccCombo) {
1765  m_fccCombo->setDefaultCollection(sentMailCol);
1766  } else {
1767  m_fccCollection = sentMailCol;
1768  }
1769  }
1770 }
1771 
1772 void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1773 {
1774  m_fccCombo = fcc;
1775 }
1776 
1777 Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1778 {
1779  return m_fccCombo;
1780 }
1781 
1783 {
1784  m_from = from;
1785 }
1786 
1787 void ComposerViewBase::setSubject(const QString &subject)
1788 {
1789  m_subject = subject;
1790  if (mSendLaterInfo) {
1791  mSendLaterInfo->setSubject(m_subject);
1792  mSendLaterInfo->setTo(to());
1793  }
1794 }
1795 
1796 void ComposerViewBase::setAutoSaveInterval(int interval)
1797 {
1798  m_autoSaveInterval = interval;
1799 }
1800 
1801 void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1802 {
1803  m_sign = sign;
1804  m_encrypt = encrypt;
1805  m_cryptoMessageFormat = format;
1806  m_neverEncrypt = neverEncryptDrafts;
1807 }
1808 
1809 void ComposerViewBase::setCharsets(const QVector<QByteArray> &charsets)
1810 {
1811  m_charsets = charsets;
1812 }
1813 
1814 void ComposerViewBase::setMDNRequested(bool mdnRequested)
1815 {
1816  m_mdnRequested = mdnRequested;
1817 }
1818 
1819 void ComposerViewBase::setUrgent(bool urgent)
1820 {
1821  m_urgent = urgent;
1822 }
1823 
1824 int ComposerViewBase::autoSaveInterval() const
1825 {
1826  return m_autoSaveInterval;
1827 }
1828 
1829 //-----------------------------------------------------------------------------
1830 void ComposerViewBase::collectImages(KMime::Content *root)
1831 {
1832  if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1833  KMime::Content *parentnode = n->parent();
1834  if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1836  while (node) {
1837  if (node->contentType()->isImage()) {
1838  qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1839  QImage img;
1840  img.loadFromData(node->decodedContent());
1841  m_editor->composerControler()->composerImages()->loadImage(
1842  img,
1843  QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1844  node->contentType()->name());
1845  }
1847  }
1848  }
1849  }
1850 }
1851 
1852 //-----------------------------------------------------------------------------
1853 bool ComposerViewBase::inlineSigningEncryptionSelected() const
1854 {
1855  if (!m_sign && !m_encrypt) {
1856  return false;
1857  }
1858  return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1859 }
1860 
1861 bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1862 {
1863  if (attachmentKeywords.isEmpty()) {
1864  return false;
1865  }
1866  if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1867  return false;
1868  }
1869 
1870  return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1871 }
1872 
1873 ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1874 {
1875  if (!hasMissingAttachments(attachmentKeywords)) {
1876  return NoMissingAttachmentFound;
1877  }
1878  int rc = KMessageBox::warningYesNoCancel(m_editor,
1879  i18n("The message you have composed seems to refer to an "
1880  "attached file but you have not attached anything.\n"
1881  "Do you want to attach a file to your message?"),
1882  i18n("File Attachment Reminder"),
1883  KGuiItem(i18n("&Attach File...")),
1884  KGuiItem(i18n("&Send as Is")));
1885  if (rc == KMessageBox::Cancel) {
1886  return FoundMissingAttachmentAndCancel;
1887  }
1888  if (rc == KMessageBox::Yes) {
1889  m_attachmentController->showAddAttachmentFileDialog();
1890  return FoundMissingAttachmentAndAddedAttachment;
1891  }
1892 
1893  return FoundMissingAttachmentAndSending;
1894 }
1895 
1896 void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1897 {
1898  if (m_attachmentModel) {
1899  const auto attachments = m_attachmentModel->attachments();
1900  for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1901  attachment->setSigned(sign);
1902  }
1903  }
1904 }
1905 
1906 void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1907 {
1908  if (m_attachmentModel) {
1909  const auto attachments = m_attachmentModel->attachments();
1910  for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1911  attachment->setEncrypted(encrypt);
1912  }
1913  }
1914 }
1915 
1916 bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1917 {
1918  bool sign = false;
1919  switch (keyResolver->checkSigningPreferences(signSomething)) {
1920  case Kleo::DoIt:
1921  if (!signSomething) {
1922  markAllAttachmentsForSigning(true);
1923  return true;
1924  }
1925  sign = true;
1926  break;
1927  case Kleo::DontDoIt:
1928  sign = false;
1929  break;
1930  case Kleo::AskOpportunistic:
1931  assert(0);
1932  case Kleo::Ask: {
1933  // the user wants to be asked or has to be asked
1935  const QString msg = i18n(
1936  "Examination of the recipient's signing preferences "
1937  "yielded that you be asked whether or not to sign "
1938  "this message.\n"
1939  "Sign this message?");
1940  switch (
1941  KMessageBox::questionYesNoCancel(m_parentWidget, msg, i18n("Sign Message?"), KGuiItem(i18nc("to sign", "&Sign")), KGuiItem(i18n("Do &Not Sign")))) {
1942  case KMessageBox::Cancel:
1943  result = false;
1944  canceled = true;
1945  return false;
1946  case KMessageBox::Yes:
1947  markAllAttachmentsForSigning(true);
1948  return true;
1949  case KMessageBox::No:
1950  markAllAttachmentsForSigning(false);
1951  return false;
1952  default:
1953  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1954  return false;
1955  }
1956  break;
1957  }
1958  case Kleo::Conflict: {
1959  // warn the user that there are conflicting signing preferences
1961  const QString msg = i18n(
1962  "There are conflicting signing preferences "
1963  "for these recipients.\n"
1964  "Sign this message?");
1965  switch (
1966  KMessageBox::warningYesNoCancel(m_parentWidget, msg, i18n("Sign Message?"), KGuiItem(i18nc("to sign", "&Sign")), KGuiItem(i18n("Do &Not Sign")))) {
1967  case KMessageBox::Cancel:
1968  result = false;
1969  canceled = true;
1970  return false;
1971  case KMessageBox::Yes:
1972  markAllAttachmentsForSigning(true);
1973  return true;
1974  case KMessageBox::No:
1975  markAllAttachmentsForSigning(false);
1976  return false;
1977  default:
1978  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1979  return false;
1980  }
1981  break;
1982  }
1983  case Kleo::Impossible: {
1985  const QString msg = i18n(
1986  "You have requested to sign this message, "
1987  "but no valid signing keys have been configured "
1988  "for this identity.");
1989  if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18n("Send Unsigned?"), KGuiItem(i18n("Send &Unsigned"))) == KMessageBox::Cancel) {
1990  result = false;
1991  return false;
1992  } else {
1993  markAllAttachmentsForSigning(false);
1994  return false;
1995  }
1996  }
1997  }
1998 
1999  if (!sign || !doSignCompletely) {
2000  if (MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned()) {
2002  const QString msg = sign && !doSignCompletely ? i18n(
2003  "Some parts of this message will not be signed.\n"
2004  "Sending only partially signed messages might violate site policy.\n"
2005  "Sign all parts instead?") // oh, I hate this...
2006  : i18n(
2007  "This message will not be signed.\n"
2008  "Sending unsigned message might violate site policy.\n"
2009  "Sign message instead?"); // oh, I hate this...
2010  const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
2011  switch (
2012  KMessageBox::warningYesNoCancel(m_parentWidget, msg, i18n("Unsigned-Message Warning"), KGuiItem(buttonText), KGuiItem(i18n("Send &As Is")))) {
2013  case KMessageBox::Cancel:
2014  result = false;
2015  canceled = true;
2016  return false;
2017  case KMessageBox::Yes:
2018  markAllAttachmentsForSigning(true);
2019  return true;
2020  case KMessageBox::No:
2021  return sign || doSignCompletely;
2022  default:
2023  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2024  return false;
2025  }
2026  }
2027  }
2028  return sign || doSignCompletely;
2029 }
2030 
2031 bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2032  Kleo::KeyResolver *keyResolver,
2033  bool encryptSomething,
2034  bool signSomething,
2035  bool &result,
2036  bool &canceled)
2037 {
2038  bool encrypt = false;
2039  bool opportunistic = false;
2040  switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2041  case Kleo::DoIt:
2042  if (!encryptSomething) {
2043  markAllAttachmentsForEncryption(true);
2044  return true;
2045  }
2046  encrypt = true;
2047  break;
2048  case Kleo::DontDoIt:
2049  encrypt = false;
2050  break;
2051  case Kleo::AskOpportunistic:
2052  opportunistic = true;
2053  // fall through...
2054  Q_FALLTHROUGH();
2055  case Kleo::Ask: {
2056  // the user wants to be asked or has to be asked
2058  const QString msg = opportunistic ? i18n(
2059  "Valid trusted encryption keys were found for all recipients.\n"
2060  "Encrypt this message?")
2061  : i18n(
2062  "Examination of the recipient's encryption preferences "
2063  "yielded that you be asked whether or not to encrypt "
2064  "this message.\n"
2065  "Encrypt this message?");
2066  switch (KMessageBox::questionYesNoCancel(m_parentWidget,
2067  msg,
2068  i18n("Encrypt Message?"),
2069  KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2070  KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2071  case KMessageBox::Cancel:
2072  result = false;
2073  canceled = true;
2074  return false;
2075  case KMessageBox::Yes:
2076  markAllAttachmentsForEncryption(true);
2077  return true;
2078  case KMessageBox::No:
2079  markAllAttachmentsForEncryption(false);
2080  return false;
2081  default:
2082  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2083  return false;
2084  }
2085  break;
2086  }
2087  case Kleo::Conflict: {
2088  // warn the user that there are conflicting encryption preferences
2090  const QString msg = i18n(
2091  "There are conflicting encryption preferences "
2092  "for these recipients.\n"
2093  "Encrypt this message?");
2094  switch (KMessageBox::warningYesNoCancel(m_parentWidget, msg, i18n("Encrypt Message?"), KGuiItem(i18n("&Encrypt")), KGuiItem(i18n("Do &Not Encrypt")))) {
2095  case KMessageBox::Cancel:
2096  result = false;
2097  canceled = true;
2098  return false;
2099  case KMessageBox::Yes:
2100  markAllAttachmentsForEncryption(true);
2101  return true;
2102  case KMessageBox::No:
2103  markAllAttachmentsForEncryption(false);
2104  return false;
2105  default:
2106  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2107  return false;
2108  }
2109  break;
2110  }
2111  case Kleo::Impossible: {
2113  const QString msg = i18n(
2114  "You have requested to encrypt this message, "
2115  "and to encrypt a copy to yourself, "
2116  "but no valid trusted encryption keys have been "
2117  "configured for this identity.");
2118  if (KMessageBox::warningContinueCancel(m_parentWidget, msg, i18n("Send Unencrypted?"), KGuiItem(i18n("Send &Unencrypted"))) == KMessageBox::Cancel) {
2119  result = false;
2120  return false;
2121  } else {
2122  markAllAttachmentsForEncryption(false);
2123  return false;
2124  }
2125  }
2126  }
2127 
2128  if (!encrypt || !doEncryptCompletely) {
2129  if (MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted()) {
2131  const QString msg = !doEncryptCompletely ? i18n(
2132  "Some parts of this message will not be encrypted.\n"
2133  "Sending only partially encrypted messages might violate "
2134  "site policy and/or leak sensitive information.\n"
2135  "Encrypt all parts instead?") // oh, I hate this...
2136  : i18n(
2137  "This message will not be encrypted.\n"
2138  "Sending unencrypted messages might violate site policy and/or "
2139  "leak sensitive information.\n"
2140  "Encrypt messages instead?"); // oh, I hate this...
2141  const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2142  switch (KMessageBox::warningYesNoCancel(m_parentWidget,
2143  msg,
2144  i18n("Unencrypted Message Warning"),
2145  KGuiItem(buttonText),
2146  KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2147  case KMessageBox::Cancel:
2148  result = false;
2149  canceled = true;
2150  return false;
2151  case KMessageBox::Yes:
2152  markAllAttachmentsForEncryption(true);
2153  return true;
2154  case KMessageBox::No:
2155  return encrypt || doEncryptCompletely;
2156  default:
2157  qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2158  return false;
2159  }
2160  }
2161  }
2162 
2163  return encrypt || doEncryptCompletely;
2164 }
2165 
2166 void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2167 {
2168  mSendLaterInfo.reset(info);
2169 }
2170 
2171 SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2172 {
2173  return mSendLaterInfo.get();
2174 }
2175 
2176 void ComposerViewBase::addFollowupReminder(const QString &messageId)
2177 {
2178  if (!messageId.isEmpty()) {
2179  if (mFollowUpDate.isValid()) {
2181  job->setSubject(m_subject);
2182  job->setMessageId(messageId);
2183  job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2184  job->setFollowUpReminderDate(mFollowUpDate);
2185  job->setCollectionToDo(mFollowUpCollection);
2186  job->start();
2187  }
2188  }
2189 }
2190 
2191 void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2192 {
2193  mSendLaterInfo->setItemId(item.id());
2194 
2195  auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2196  job->start();
2197 }
2198 
2199 bool ComposerViewBase::requestDeleveryConfirmation() const
2200 {
2201  return m_requestDeleveryConfirmation;
2202 }
2203 
2204 void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2205 {
2206  m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2207 }
2208 
2209 KMime::Message::Ptr ComposerViewBase::msg() const
2210 {
2211  return m_msg;
2212 }
The RichTextComposerNg class.
static QVector< Mailbox > listFromUnicodeString(const QString &s)
bool loadFromData(const uchar *data, int len, const char *format)
bool isValid() const
void quit()
Collection::List collections() const
QByteArray subType() const
QString htmlContent() const
Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part...
QString writableLocation(QStandardPaths::StandardLocation type)
void setNameFilters(const QStringList &nameFilters)
void append(const T &value)
void push_back(const T &value)
QString errorString() const const
static TransportManager * self()
virtual QString errorString() const
bool isComposing() const
Returns true if there is at least one composer job running.
virtual bool setPermissions(QFileDevice::Permissions permissions)
The FollowupReminderCreateJob class.
QString expandedFrom() const
Returns the expanded From field.
QStringList to
The email address and optionally the name of the primary recipients.
Definition: infopart.h:29
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
bool remove(const QString &fileName)
bool isEmpty() const const
T value() const const
QString rawText(bool *ok=nullptr) const
void autoSaveMessage()
Save the message.
virtual bool open(QIODevice::OpenMode mode) override
static RecentAddresses * self(KConfig *config=nullptr)
const T & constFirst() const const
Headers::ContentDisposition * contentDisposition(bool create=true)
QString join(const QString &separator) const const
QString autocorrectionLanguage() const
void setAttachmentModel(MessageComposer::AttachmentModel *model)
The following are for setting the various options and widgets in the composer.
The GlobalPart class.
Definition: globalpart.h:19
QByteArray mimeType() const
const QLatin1String name
QByteArray encodedContent(bool useCrLf=false)
T * data() const const
void setPayload(const T &p)
void setAutoRemove(bool b)
A job to resolve nicknames, distribution lists and email addresses for queued emails.
Id id() const
void setAutoSaveFileName(const QString &fileName)
Sets the filename to use when autosaving something.
void add(const QString &entry)
void timeout()
The Composer class.
Definition: composer.h:33
QString number(int n, int base)
int count(const T &value) const const
bool exists() const const
QByteArray decodedContent()
PartitionTable::TableType type
QString fromUtf8(const char *str, int size)
void setMimeType(const QString &mimeType)
QVariant property(const char *name) const const
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
bool commit()
void send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher=true)
Send the message with the specified method, saving it in the specified folder.
int exec(QEventLoop::ProcessEventsFlags flags)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
The AttachmentModel class.
bool isEmpty() const const
bool isEmpty() const const
const char * constData() const const
A class that encapsulates an attachment.
void parseObjectTree(KMime::Content *node, bool parseOnlySingleNode=false)
Parse beginning at a given node and recursively parsing the children of that node and it&#39;s next sibli...
KCODECS_EXPORT QString normalizeAddressesAndEncodeIdn(const QString &str)
WaitCursor
Headers::ContentID * contentID(bool create=true)
QStringList cc
Carbon copy: The email address and optionally the name of the secondary recipients.
Definition: infopart.h:32
void setFrom(const QString &from)
Widgets for editing differ in client classes, so values are set before sending.
Send later information.
Definition: sendlaterinfo.h:17
The SignatureController class Controls signature (the footer thing, not the crypto thing) operations ...
static SpecialMailCollections * self()
Headers::ContentType * contentType(bool create=true)
T * data() const const
void reserve(int size)
void setContent(const QByteArray &s)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
QString path() const const
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
QVariant fromValue(const T &value)
QString i18n(const char *text, const TYPE &arg...)
ButtonCode questionYesNoCancel(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
QString & replace(int position, int n, QChar after)
const T & at(int i) const const
QStringList expandedReplyTo() const
Returns the expanded Reply-To field.
QSharedPointer< AttachmentPart > Ptr
Defines a pointer to an attachment object.
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
Removes all private header fields (e.g.
Definition: stringutil.cpp:354
ComposerViewBase::MissingAttachment checkForMissingAttachments(const QStringList &attachmentKeywords)
Check if the mail has references to attachments, but no attachments are added to it.
QDateTime currentDateTime()
QByteArray toLatin1() const const
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
The InfoPart class contains the message header.
Definition: infopart.h:21
void cleanupAutoSave()
Stop autosaving and delete the autosaved message.
The AttachmentControllerBase class.
bool isEmpty() const const
bool mkdir(const QString &dirName) const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
virtual Q_SCRIPTABLE void start()=0
QString from
The email address and optionally the name of the author of the mail.
Definition: infopart.h:26
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)
QString fcc
The name of a file, to which a copy of the sent message should be appended.
Definition: infopart.h:45
QVector< KMime::Content * > attachmentsOfExtraContents() const
Returns a list of attachments of attached extra content nodes.
Parses messages and generates HTML display code out of them.
AKONADI_MIME_EXPORT void copyMessageFlags(KMime::Message &from, Akonadi::Item &to)
qint64 write(const char *data, qint64 maxSize)
QString fromLatin1(const char *str, int size)
void setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts=false)
The following are various settings the user can modify when composing a message.
Content * parent() const
Action checkSigningPreferences(bool signingRequested) const
Determine whether to sign or not, depending on the per-recipient signing preferences, as well as the availability of usable signing keys.
QStringList expandedCc() const
Returns the expanded CC field.
QString to() const
Header fields in recipients editor.
The RecipientsEditor class.
QStringList expandedBcc() const
Returns the expanded Bcc field.
QString replyToAddr() const
A very simple ObjectTreeSource.
Akonadi::Collection defaultCollection(Type type) const
void result(KJob *job)
void updateAutoSave()
Enables/disables autosaving depending on the value of the autosave interval.
Headers::ContentDescription * contentDescription(bool create=true)
Action checkEncryptionPreferences(bool encryptionRequested) const
Determine whether to encrypt or not, depending on the per-recipient encryption preferences, as well as the availability of usable encryption keys.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int size() const const
Transport * transportById(int id, bool def=true) const
QString plainTextContent() const
The text of the message, ie.
MESSAGECORE_EXPORT KMime::Content * nextSibling(const KMime::Content *node)
Returns the next sibling node of the given node.
void addAttachment(const QUrl &url, const QString &comment, bool sync)
Add the given attachment to the message.
QString toString() const const
qlonglong toLongLong(bool *ok, int base) const const
int count(const Key &key) const const
QStringList bcc
Blind Carbon copy: The email address and optionally the name of the secondary recipients.
Definition: infopart.h:36
QUuid createUuid()
bool mkpath(const QString &dirPath) const const
QStringList expandedTo() const
Returns the expanded To field.
QUrl fromLocalFile(const QString &localFile)
void sorry(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
int error() const
void setMessage(const KMime::Message::Ptr &newMsg, bool allowDecryption)
Set the message to be opened in the composer window, and set the internal data structures to keep tra...
ButtonCode warningYesNo(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Fri Nov 26 2021 23:16:41 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.