Messagelib

urlhandlermanager.cpp
1 /* -*- c++ -*-
2  urlhandlermanager.cpp
3 
4  This file is part of KMail, the KDE mail client.
5  SPDX-FileCopyrightText: 2003 Marc Mutz <[email protected]>
6  SPDX-FileCopyrightText: 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
7  SPDX-FileCopyrightText: 2009 Andras Mantia <[email protected]>
8 
9  SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 
12 #include "urlhandlermanager.h"
13 #include "../utils/messageviewerutil_p.h"
14 #include "interfaces/bodyparturlhandler.h"
15 #include "messageviewer/messageviewerutil.h"
16 #include "messageviewer_debug.h"
17 #include "stl_util.h"
18 #include "urlhandlermanager_p.h"
19 #include "utils/mimetype.h"
20 #include "viewer/viewer_p.h"
21 
22 #include <MimeTreeParser/NodeHelper>
23 #include <MimeTreeParser/PartNodeBodyPart>
24 
25 #include <Akonadi/OpenEmailAddressJob>
26 #include <MessageCore/StringUtil>
27 #include <PimCommon/BroadcastStatus>
28 
29 #include <Akonadi/ContactSearchJob>
30 
31 #include <Akonadi/MessageFlags>
32 #include <KEmailAddress>
33 #include <KMbox/MBox>
34 #include <KMime/Content>
35 
36 #include <KIconLoader>
37 #include <KLocalizedString>
38 #include <KMessageBox>
39 
40 #include <QApplication>
41 #include <QClipboard>
42 #include <QDrag>
43 #include <QFile>
44 #include <QIcon>
45 #include <QMenu>
46 #include <QMimeData>
47 #include <QMimeDatabase>
48 #include <QProcess>
49 #include <QStandardPaths>
50 #include <QUrl>
51 #include <QUrlQuery>
52 
53 #include <algorithm>
54 
55 #include <Libkleo/MessageBox>
56 #include <chrono>
57 
58 using namespace std::chrono_literals;
59 
60 using std::for_each;
61 using std::remove;
62 using namespace MessageViewer;
63 using namespace MessageCore;
64 
65 URLHandlerManager *URLHandlerManager::self = nullptr;
66 
67 //
68 //
69 // BodyPartURLHandlerManager
70 //
71 //
72 
73 BodyPartURLHandlerManager::~BodyPartURLHandlerManager()
74 {
75  for_each(mHandlers.begin(), mHandlers.end(), [](QVector<const Interface::BodyPartURLHandler *> &handlers) {
76  for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero<Interface::BodyPartURLHandler>());
77  });
78 }
79 
80 void BodyPartURLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
81 {
82  if (!handler) {
83  return;
84  }
85  unregisterHandler(handler); // don't produce duplicates
86  const auto mt = mimeType.toLatin1();
87  auto it = mHandlers.find(mt);
88  if (it == mHandlers.end()) {
89  it = mHandlers.insert(mt, {});
90  }
91  it->push_back(handler);
92 }
93 
94 void BodyPartURLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
95 {
96  // don't delete them, only remove them from the list!
97  auto it = mHandlers.begin();
98  while (it != mHandlers.end()) {
99  it->erase(remove(it->begin(), it->end(), handler), it->end());
100  if (it->isEmpty()) {
101  it = mHandlers.erase(it);
102  } else {
103  ++it;
104  }
105  }
106 }
107 
108 static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path)
109 {
110  Q_ASSERT(path);
111 
112  if (!w || url.scheme() != QLatin1String("x-kmail")) {
113  return nullptr;
114  }
115  const QString urlPath = url.path();
116 
117  // urlPath format is: /bodypart/<random number>/<part id>/<path>
118 
119  qCDebug(MESSAGEVIEWER_LOG) << "BodyPartURLHandler: urlPath ==" << urlPath;
120  if (!urlPath.startsWith(QLatin1String("/bodypart/"))) {
121  return nullptr;
122  }
123 
124  const QStringList urlParts = urlPath.mid(10).split(QLatin1Char('/'));
125  if (urlParts.size() != 3) {
126  return nullptr;
127  }
128  // KMime::ContentIndex index( urlParts[1] );
129  QByteArray query(urlParts.at(2).toLatin1());
130  if (url.hasQuery()) {
131  query += "?" + url.query().toLatin1();
132  }
134  return w->nodeFromUrl(QUrl(urlParts.at(1)));
135 }
136 
137 QVector<const Interface::BodyPartURLHandler *> BodyPartURLHandlerManager::handlersForPart(KMime::Content *node) const
138 {
139  if (auto ct = node->contentType(false)) {
140  auto mimeType = ct->mimeType();
141  if (!mimeType.isEmpty()) {
142  // Bug 390900
143  if (mimeType == "text/x-vcard") {
144  mimeType = "text/vcard";
145  }
146  return mHandlers.value(mimeType);
147  }
148  }
149 
150  return {};
151 }
152 
153 bool BodyPartURLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
154 {
155  QString path;
156  KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
157  if (!node) {
158  return false;
159  }
160 
161  MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
162 
163  for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
164  for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
165  if ((*it)->handleClick(w->viewer(), &part, path)) {
166  return true;
167  }
168  }
169  }
170 
171  return false;
172 }
173 
174 bool BodyPartURLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
175 {
176  QString path;
177  KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
178  if (!node) {
179  return false;
180  }
181 
182  MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
183 
184  for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
185  for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
186  if ((*it)->handleContextMenuRequest(&part, path, p)) {
187  return true;
188  }
189  }
190  }
191  return false;
192 }
193 
194 QString BodyPartURLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
195 {
196  QString path;
197  KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
198  if (!node) {
199  return {};
200  }
201 
202  MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
203 
204  for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
205  for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
206  const QString msg = (*it)->statusBarMessage(&part, path);
207  if (!msg.isEmpty()) {
208  return msg;
209  }
210  }
211  }
212  return {};
213 }
214 
215 //
216 //
217 // URLHandlerManager
218 //
219 //
220 
221 URLHandlerManager::URLHandlerManager()
222 {
223  registerHandler(new KMailProtocolURLHandler());
224  registerHandler(new ExpandCollapseQuoteURLManager());
225  registerHandler(new SMimeURLHandler());
226  registerHandler(new MailToURLHandler());
227  registerHandler(new ContactUidURLHandler());
228  registerHandler(new HtmlAnchorHandler());
229  registerHandler(new AttachmentURLHandler());
230  registerHandler(mBodyPartURLHandlerManager = new BodyPartURLHandlerManager());
231  registerHandler(new ShowAuditLogURLHandler());
232  registerHandler(new InternalImageURLHandler);
233  registerHandler(new KRunURLHandler());
234  registerHandler(new EmbeddedImageURLHandler());
235 }
236 
237 URLHandlerManager::~URLHandlerManager()
238 {
239  for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero<MimeTreeParser::URLHandler>());
240 }
241 
242 URLHandlerManager *URLHandlerManager::instance()
243 {
244  if (!self) {
245  self = new URLHandlerManager();
246  }
247  return self;
248 }
249 
250 void URLHandlerManager::registerHandler(const MimeTreeParser::URLHandler *handler)
251 {
252  if (!handler) {
253  return;
254  }
255  unregisterHandler(handler); // don't produce duplicates
256  mHandlers.push_back(handler);
257 }
258 
259 void URLHandlerManager::unregisterHandler(const MimeTreeParser::URLHandler *handler)
260 {
261  // don't delete them, only remove them from the list!
262  mHandlers.erase(remove(mHandlers.begin(), mHandlers.end(), handler), mHandlers.end());
263 }
264 
265 void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
266 {
267  if (mBodyPartURLHandlerManager) {
268  mBodyPartURLHandlerManager->registerHandler(handler, mimeType);
269  }
270 }
271 
272 void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
273 {
274  if (mBodyPartURLHandlerManager) {
275  mBodyPartURLHandlerManager->unregisterHandler(handler);
276  }
277 }
278 
279 bool URLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
280 {
281  HandlerList::const_iterator end(mHandlers.constEnd());
282  for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
283  if ((*it)->handleClick(url, w)) {
284  return true;
285  }
286  }
287  return false;
288 }
289 
290 bool URLHandlerManager::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
291 {
292  HandlerList::const_iterator end(mHandlers.constEnd());
293  for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
294  if ((*it)->handleShiftClick(url, window)) {
295  return true;
296  }
297  }
298  return false;
299 }
300 
301 bool URLHandlerManager::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
302 {
303  HandlerList::const_iterator end(mHandlers.constEnd());
304 
305  for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
306  if ((*it)->willHandleDrag(url, window)) {
307  return true;
308  }
309  }
310  return false;
311 }
312 
313 bool URLHandlerManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
314 {
315  HandlerList::const_iterator end(mHandlers.constEnd());
316  for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
317  if ((*it)->handleDrag(url, window)) {
318  return true;
319  }
320  }
321  return false;
322 }
323 
324 bool URLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
325 {
326  HandlerList::const_iterator end(mHandlers.constEnd());
327  for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
328  if ((*it)->handleContextMenuRequest(url, p, w)) {
329  return true;
330  }
331  }
332  return false;
333 }
334 
335 QString URLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
336 {
337  HandlerList::const_iterator end(mHandlers.constEnd());
338  for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
339  const QString msg = (*it)->statusBarMessage(url, w);
340  if (!msg.isEmpty()) {
341  return msg;
342  }
343  }
344  return {};
345 }
346 
347 //
348 //
349 // URLHandler
350 //
351 //
352 
353 bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
354 {
355  if (url.scheme() == QLatin1String("kmail")) {
356  if (!w) {
357  return false;
358  }
359  const QString urlPath(url.path());
360  if (urlPath == QLatin1String("showHTML")) {
361  w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html);
362  w->update(MimeTreeParser::Force);
363  return true;
364  } else if (urlPath == QLatin1String("goOnline")) {
365  w->goOnline();
366  return true;
367  } else if (urlPath == QLatin1String("goResourceOnline")) {
368  w->goResourceOnline();
369  return true;
370  } else if (urlPath == QLatin1String("loadExternal")) {
371  w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride());
372  w->update(MimeTreeParser::Force);
373  return true;
374  } else if (urlPath == QLatin1String("decryptMessage")) {
375  w->setDecryptMessageOverwrite(true);
376  w->update(MimeTreeParser::Force);
377  return true;
378  } else if (urlPath == QLatin1String("showSignatureDetails")) {
379  w->setShowSignatureDetails(true);
380  w->update(MimeTreeParser::Force);
381  return true;
382  } else if (urlPath == QLatin1String("hideSignatureDetails")) {
383  w->setShowSignatureDetails(false);
384  w->update(MimeTreeParser::Force);
385  return true;
386  } else if (urlPath == QLatin1String("showEncryptionDetails")) {
387  w->setShowEncryptionDetails(true);
388  w->update(MimeTreeParser::Force);
389  return true;
390  } else if (urlPath == QLatin1String("hideEncryptionDetails")) {
391  w->setShowEncryptionDetails(false);
392  w->update(MimeTreeParser::Force);
393  return true;
394  }
395  }
396  return false;
397 }
398 
399 QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
400 {
401  const QString schemeStr = url.scheme();
402  if (schemeStr == QLatin1String("kmail")) {
403  const QString urlPath(url.path());
404  if (urlPath == QLatin1String("showHTML")) {
405  return i18n("Turn on HTML rendering for this message.");
406  } else if (urlPath == QLatin1String("loadExternal")) {
407  return i18n("Load external references from the Internet for this message.");
408  } else if (urlPath == QLatin1String("goOnline")) {
409  return i18n("Work online.");
410  } else if (urlPath == QLatin1String("goResourceOnline")) {
411  return i18n("Make account online.");
412  } else if (urlPath == QLatin1String("decryptMessage")) {
413  return i18n("Decrypt message.");
414  } else if (urlPath == QLatin1String("showSignatureDetails")) {
415  return i18n("Show signature details.");
416  } else if (urlPath == QLatin1String("hideSignatureDetails")) {
417  return i18n("Hide signature details.");
418  } else if (urlPath == QLatin1String("showEncryptionDetails")) {
419  return i18n("Show encryption details.");
420  } else if (urlPath == QLatin1String("hideEncryptionDetails")) {
421  return i18n("Hide encryption details.");
422  } else {
423  return {};
424  }
425  } else if (schemeStr == QLatin1String("help")) {
426  return i18n("Open Documentation");
427  }
428  return {};
429 }
430 
431 bool ExpandCollapseQuoteURLManager::handleClick(const QUrl &url, ViewerPrivate *w) const
432 {
433  // kmail:levelquote/?num -> the level quote to collapse.
434  // kmail:levelquote/?-num -> expand all levels quote.
435  if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("levelquote")) {
436  const QString levelStr = url.query();
437  bool isNumber = false;
438  const int levelQuote = levelStr.toInt(&isNumber);
439  if (isNumber) {
440  w->slotLevelQuote(levelQuote);
441  }
442  return true;
443  }
444  return false;
445 }
446 
447 bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
448 {
449  Q_UNUSED(url)
450  Q_UNUSED(window)
451  return false;
452 }
453 
454 QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const
455 {
456  if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("levelquote")) {
457  const QString query = url.query();
458  if (query.length() >= 1) {
459  if (query[0] == QLatin1Char('-')) {
460  return i18n("Expand all quoted text.");
461  } else {
462  return i18n("Collapse quoted text.");
463  }
464  }
465  }
466  return {};
467 }
468 
469 bool foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId)
470 {
471  static QString showCertMan(QStringLiteral("showCertificate#"));
472  displayName.clear();
473  libName.clear();
474  keyId.clear();
475  int i1 = aUrl.indexOf(showCertMan);
476  if (-1 < i1) {
477  i1 += showCertMan.length();
478  int i2 = aUrl.indexOf(QLatin1String(" ### "), i1);
479  if (i1 < i2) {
480  displayName = aUrl.mid(i1, i2 - i1);
481  i1 = i2 + 5;
482  i2 = aUrl.indexOf(QLatin1String(" ### "), i1);
483  if (i1 < i2) {
484  libName = aUrl.mid(i1, i2 - i1);
485  i2 += 5;
486 
487  keyId = aUrl.mid(i2);
488  /*
489  int len = aUrl.length();
490  if( len > i2+1 ) {
491  keyId = aUrl.mid( i2, 2 );
492  i2 += 2;
493  while( len > i2+1 ) {
494  keyId += ':';
495  keyId += aUrl.mid( i2, 2 );
496  i2 += 2;
497  }
498  }
499  */
500  }
501  }
502  }
503  return !keyId.isEmpty();
504 }
505 
506 bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
507 {
508  if (!url.hasFragment()) {
509  return false;
510  }
512  QString libName;
513  QString keyId;
514  if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) {
515  return false;
516  }
517  QStringList lst;
518  lst << QStringLiteral("--parent-windowid") << QString::number(static_cast<qlonglong>(w->viewer()->mainWindow()->winId())) << QStringLiteral("--query")
519  << keyId;
520  const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
521  if (exec.isEmpty()) {
522  qCWarning(MESSAGEVIEWER_LOG) << "Could not find kleopatra executable in PATH";
523  KMessageBox::error(w->mMainWindow,
524  i18n("Could not start certificate manager. "
525  "Please check your installation."),
526  i18n("KMail Error"));
527  return false;
528  }
529  QProcess::startDetached(exec, lst);
530  return true;
531 }
532 
533 QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
534 {
536  QString libName;
537  QString keyId;
538  if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) {
539  return {};
540  }
541  return i18n("Show certificate 0x%1", keyId);
542 }
543 
544 bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
545 {
546  if (!url.host().isEmpty() || !url.hasFragment()) {
547  return false;
548  }
549 
550  w->scrollToAnchor(url.fragment());
551  return true;
552 }
553 
554 QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
555 {
556  if (url.scheme() == QLatin1String("mailto")) {
557  return KEmailAddress::decodeMailtoUrl(url);
558  }
559  return {};
560 }
561 
562 static QString searchFullEmailByUid(const QString &uid)
563 {
564  QString fullEmail;
565  auto job = new Akonadi::ContactSearchJob();
566  job->setLimit(1);
568  job->exec();
569  const KContacts::Addressee::List res = job->contacts();
570  if (!res.isEmpty()) {
571  KContacts::Addressee addr = res.at(0);
572  fullEmail = addr.fullEmail();
573  }
574  return fullEmail;
575 }
576 
577 static void runKAddressBook(const QUrl &url)
578 {
579  auto job = new Akonadi::OpenEmailAddressJob(url.path(), nullptr);
580  job->start();
581 }
582 
583 bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const
584 {
585  if (url.scheme() == QLatin1String("uid")) {
586  runKAddressBook(url);
587  return true;
588  } else {
589  return false;
590  }
591 }
592 
593 bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const
594 {
595  if (url.scheme() != QLatin1String("uid") || url.path().isEmpty()) {
596  return false;
597  }
598 
599  QMenu menu;
600  QAction *open = menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book"));
601 #ifndef QT_NO_CLIPBOARD
602  QAction *copy = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address"));
603 #endif
604 
605  QAction *a = menu.exec(p);
606  if (a == open) {
607  runKAddressBook(url);
608 #ifndef QT_NO_CLIPBOARD
609  } else if (a == copy) {
610  const QString fullEmail = searchFullEmailByUid(url.path());
611  if (!fullEmail.isEmpty()) {
613  clip->setText(fullEmail, QClipboard::Clipboard);
614  clip->setText(fullEmail, QClipboard::Selection);
615  PimCommon::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
616  }
617 #endif
618  }
619 
620  return true;
621 }
622 
623 QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
624 {
625  if (url.scheme() == QLatin1String("uid")) {
626  return i18n("Lookup the contact in KAddressbook");
627  } else {
628  return {};
629  }
630 }
631 
632 KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const
633 {
634  if (!w || !w->mMessage) {
635  return nullptr;
636  }
637  if (url.scheme() == QLatin1String("attachment")) {
638  KMime::Content *node = w->nodeFromUrl(url);
639  return node;
640  }
641  return nullptr;
642 }
643 
644 bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const
645 {
646  bool inHeader = false;
647  QUrlQuery query(url);
648  const QString place = query.queryItemValue(QStringLiteral("place")).toLower();
649  if (!place.isNull()) {
650  inHeader = (place == QLatin1String("header"));
651  }
652  return inHeader;
653 }
654 
655 bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
656 {
657  KMime::Content *node = nodeForUrl(url, w);
658  if (!node) {
659  return false;
660  }
661  const bool inHeader = attachmentIsInHeader(url);
662  const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
663  if (inHeader) {
664  w->scrollToAttachment(node);
665  }
666  // if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
667  w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
668  //}
669 
670  return true;
671 }
672 
673 bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
674 {
675  KMime::Content *node = nodeForUrl(url, window);
676  if (!node) {
677  return false;
678  }
679  if (!window) {
680  return false;
681  }
682  if (node->contentType()->mimeType() == "text/x-moz-deleted") {
683  return false;
684  }
685 
686  const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
687  if (isEncapsulatedMessage) {
689  message->setContent(node->parent()->bodyAsMessage()->encodedContent());
690  message->parse();
691  Akonadi::Item item;
695  QUrl newUrl;
696  if (MessageViewer::Util::saveMessageInMboxAndGetUrl(newUrl, Akonadi::Item::List() << item, window->viewer())) {
697  window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl);
698  }
699  } else {
700  QList<QUrl> urlList;
701  if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) {
702  window->viewer()->showOpenAttachmentFolderWidget(urlList);
703  }
704  }
705 
706  return true;
707 }
708 
709 bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
710 {
711  return nodeForUrl(url, window) != nullptr;
712 }
713 
714 bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
715 {
716 #ifndef QT_NO_DRAGANDDROP
717  KMime::Content *node = nodeForUrl(url, window);
718  if (!node) {
719  return false;
720  }
721  if (node->contentType()->mimeType() == "text/x-moz-deleted") {
722  return false;
723  }
724  QString fileName;
725  QUrl tUrl;
726  const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
727  if (isEncapsulatedMessage) {
729  message->setContent(node->parent()->bodyAsMessage()->encodedContent());
730  message->parse();
731  Akonadi::Item item;
735  fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
736 
737  KMBox::MBox mbox;
738  QFile::remove(fileName);
739 
740  if (!mbox.load(fileName)) {
741  qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file";
742  return false;
743  }
745 
746  if (!mbox.save()) {
747  qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file";
748  return false;
749  }
750  tUrl = QUrl::fromLocalFile(fileName);
751  } else {
752  if (node->header<KMime::Headers::Subject>()) {
753  if (!node->contents().isEmpty()) {
754  node = node->contents().constLast();
755  fileName = window->nodeHelper()->writeNodeToTempFile(node);
756  tUrl = QUrl::fromLocalFile(fileName);
757  }
758  }
759  if (fileName.isEmpty()) {
760  tUrl = window->nodeHelper()->tempFileUrlFromNode(node);
761  fileName = tUrl.path();
762  }
763  }
764  if (!fileName.isEmpty()) {
765  QFile f(fileName);
767  const QString icon = Util::iconPathForContent(node, KIconLoader::Small);
768  auto drag = new QDrag(window->viewer());
769  auto mimeData = new QMimeData();
770  mimeData->setUrls(QList<QUrl>() << tUrl);
771  drag->setMimeData(mimeData);
772  if (!icon.isEmpty()) {
773  drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
774  }
775  drag->exec();
776  return true;
777  } else {
778 #endif
779  return false;
780  }
781 }
782 
783 bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
784 {
785  KMime::Content *node = nodeForUrl(url, w);
786  if (!node) {
787  return false;
788  }
789  // PENDING(romain_kdab) : replace with toLocalFile() ?
790  w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
791  return true;
792 }
793 
794 QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
795 {
796  KMime::Content *node = nodeForUrl(url, w);
797  if (!node) {
798  return {};
799  }
801  if (!name.isEmpty()) {
802  return i18n("Attachment: %1", name);
803  } else if (dynamic_cast<KMime::Message *>(node)) {
804  if (node->header<KMime::Headers::Subject>()) {
805  return i18n("Encapsulated Message (Subject: %1)", node->header<KMime::Headers::Subject>()->asUnicodeString());
806  } else {
807  return i18n("Encapsulated Message");
808  }
809  }
810  return i18n("Unnamed attachment");
811 }
812 
813 static QString extractAuditLog(const QUrl &url)
814 {
815  if (url.scheme() != QLatin1String("kmail") || url.path() != QLatin1String("showAuditLog")) {
816  return {};
817  }
818  QUrlQuery query(url);
819  Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty());
820  return query.queryItemValue(QStringLiteral("log"));
821 }
822 
823 bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
824 {
825  const QString auditLog = extractAuditLog(url);
826  if (auditLog.isEmpty()) {
827  return false;
828  }
829  Kleo::MessageBox::auditLog(w->mMainWindow, auditLog);
830  return true;
831 }
832 
833 bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const
834 {
835  Q_UNUSED(w)
836  // disable RMB for my own links:
837  return !extractAuditLog(url).isEmpty();
838 }
839 
840 QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
841 {
842  if (extractAuditLog(url).isEmpty()) {
843  return {};
844  } else {
845  return i18n("Show GnuPG Audit Log for this operation");
846  }
847 }
848 
849 bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
850 {
851  Q_UNUSED(url)
852  Q_UNUSED(window)
853  return true;
854 }
855 
856 bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
857 {
858  Q_UNUSED(window)
859  Q_UNUSED(url)
860 
861  // This will only be called when willHandleDrag() was true. Return false here, that will
862  // notify ViewerPrivate::eventFilter() that no drag was started.
863  return false;
864 }
865 
866 bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
867 {
868  Q_UNUSED(window)
869  if (url.scheme() == QLatin1String("data") && url.path().startsWith(QLatin1String("image"))) {
870  return true;
871  }
872 
873  const QString imagePath =
875  return url.path().contains(imagePath);
876 }
877 
878 bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
879 {
880  const QString scheme(url.scheme());
881  if ((scheme == QLatin1String("http")) || (scheme == QLatin1String("https")) || (scheme == QLatin1String("ftp")) || (scheme == QLatin1String("file"))
882  || (scheme == QLatin1String("ftps")) || (scheme == QLatin1String("sftp")) || (scheme == QLatin1String("help")) || (scheme == QLatin1String("vnc"))
883  || (scheme == QLatin1String("smb")) || (scheme == QLatin1String("fish")) || (scheme == QLatin1String("news")) || (scheme == QLatin1String("tel"))
884  || (scheme == QLatin1String("geo"))) {
885  PimCommon::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL..."));
886  QTimer::singleShot(2s, PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::reset);
887 
888  QMimeDatabase mimeDb;
889  auto mime = mimeDb.mimeTypeForUrl(url);
890  if (mime.name() == QLatin1String("application/x-desktop") || mime.name() == QLatin1String("application/x-executable")
891  || mime.name() == QLatin1String("application/x-ms-dos-executable") || mime.name() == QLatin1String("application/x-shellscript")) {
893  nullptr,
894  xi18nc("@info", "Do you really want to execute <filename>%1</filename>?", url.toDisplayString(QUrl::PreferLocalFile)),
895  QString(),
896  KGuiItem(i18n("Execute")),
898  != KMessageBox::ButtonCode::PrimaryAction) {
899  return true;
900  }
901  }
902  w->checkPhishingUrl();
903  return true;
904  } else {
905  return false;
906  }
907 }
908 
909 bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
910 {
911  Q_UNUSED(url)
912  Q_UNUSED(window)
913  return false;
914 }
915 
916 bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
917 {
918  Q_UNUSED(window)
919  return url.scheme() == QLatin1String("cid");
920 }
An interface to reader link handlers.
Definition: urlhandler.h:29
bool isNull() const const
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
bool save(const QString &fileName=QString())
static QString fileName(const KMime::Content *node)
Returns a usable filename for a node, that can be the filename from the content disposition header,...
QString number(int n, int base)
QMimeType mimeTypeForUrl(const QUrl &url) const const
QByteArray mimeType() const
bool remove()
Content * parent() const
bool hasQuery() const const
QString scheme() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool bodyIsMessage() const
int length() const const
void clear()
T * header(bool create=false)
QString asUnicodeString() const override
QIcon fromTheme(const QString &name)
QString fromPercentEncoding(const QByteArray &input)
void setMimeType(const QString &mimeType)
QString query(QUrl::ComponentFormattingOptions options) const const
QByteArray toLatin1() const const
KCALUTILS_EXPORT QString mimeType()
bool hasFragment() const const
an implementation of the BodyPart interface using KMime::Content's
MBoxEntry appendMessage(const KMime::Message::Ptr &message)
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
AKONADI_MIME_EXPORT void copyMessageFlags(KMime::Message &from, Akonadi::Item &to)
QString findExecutable(const QString &executableName, const QStringList &paths)
QAction * addAction(const QString &text)
void push_back(char ch)
KGuiItem cancel()
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QClipboard * clipboard()
KCODECS_EXPORT QString decodeMailtoUrl(const QUrl &mailtoUrl)
int size() const const
static QString mimeType()
QString i18n(const char *text, const TYPE &arg...)
PreferLocalFile
bool isEmpty() const const
bool load(const QString &fileName)
QUrl fromLocalFile(const QString &localFile)
QString toDisplayString(QUrl::FormattingOptions options) const const
const T & at(int i) const const
int toInt(bool *ok, int base) const const
KGuiItem remove()
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))
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void setText(const QString &text, QClipboard::Mode mode)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QSharedPointer< Message > bodyAsMessage() const
bool startDetached(qint64 *pid)
QString host(QUrl::ComponentFormattingOptions options) const const
Singleton to manage the list of URLHandlers.
QString fragment(QUrl::ComponentFormattingOptions options) const const
QString path(QUrl::ComponentFormattingOptions options) const const
QString fullEmail(const QString &email=QString()) const
QString path(const QString &relativePath)
QString name(StandardShortcut id)
AddresseeList List
Headers::ContentType * contentType(bool create=true)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
QString mid(int position, int n) const const
const QList< QKeySequence > & open()
QString message
const QList< QKeySequence > & end()
const QList< QKeySequence > & copy()
QAction * exec()
An interface to body part reader link handlers.
void setPayload(const T &p)
QVector< Content * > contents() const
T payload() const
QByteArray & insert(int i, char ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Mar 22 2023 04:07:15 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.