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

KDE's Doxygen guidelines are available online.