File indexing completed on 2025-03-09 04:54:41
0001 /* -*- c++ -*- 0002 urlhandlermanager.cpp 0003 0004 This file is part of KMail, the KDE mail client. 0005 SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org> 0006 SPDX-FileCopyrightText: 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net 0007 SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "urlhandlermanager.h" 0013 #include "../utils/messageviewerutil_p.h" 0014 #include "interfaces/bodyparturlhandler.h" 0015 #include "messageviewer/messageviewerutil.h" 0016 #include "messageviewer_debug.h" 0017 #include "stl_util.h" 0018 #include "urlhandlermanager_p.h" 0019 #include "utils/mimetype.h" 0020 #include "viewer/viewer_p.h" 0021 0022 #include <MimeTreeParser/NodeHelper> 0023 #include <MimeTreeParser/PartNodeBodyPart> 0024 0025 #include <Akonadi/OpenEmailAddressJob> 0026 #include <MessageCore/StringUtil> 0027 #include <PimCommon/BroadcastStatus> 0028 0029 #include <Akonadi/ContactSearchJob> 0030 0031 #include <Akonadi/MessageFlags> 0032 #include <KEmailAddress> 0033 #include <KMbox/MBox> 0034 #include <KMime/Content> 0035 0036 #include <KIconLoader> 0037 #include <KLocalizedString> 0038 #include <KMessageBox> 0039 0040 #include <QApplication> 0041 #include <QClipboard> 0042 #include <QDrag> 0043 #include <QFile> 0044 #include <QIcon> 0045 #include <QMenu> 0046 #include <QMimeData> 0047 #include <QMimeDatabase> 0048 #include <QProcess> 0049 #include <QStandardPaths> 0050 #include <QUrl> 0051 #include <QUrlQuery> 0052 0053 #include <algorithm> 0054 0055 #include <Libkleo/AuditLogEntry> 0056 #include <Libkleo/AuditLogViewer> 0057 #include <chrono> 0058 0059 using namespace std::chrono_literals; 0060 0061 using std::for_each; 0062 using std::remove; 0063 using namespace MessageViewer; 0064 using namespace MessageCore; 0065 0066 URLHandlerManager *URLHandlerManager::self = nullptr; 0067 0068 // 0069 // 0070 // BodyPartURLHandlerManager 0071 // 0072 // 0073 0074 BodyPartURLHandlerManager::~BodyPartURLHandlerManager() 0075 { 0076 for_each(mHandlers.begin(), mHandlers.end(), [](QList<const Interface::BodyPartURLHandler *> &handlers) { 0077 for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero<Interface::BodyPartURLHandler>()); 0078 }); 0079 } 0080 0081 void BodyPartURLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType) 0082 { 0083 if (!handler) { 0084 return; 0085 } 0086 unregisterHandler(handler); // don't produce duplicates 0087 const auto mt = mimeType.toLatin1(); 0088 auto it = mHandlers.find(mt); 0089 if (it == mHandlers.end()) { 0090 it = mHandlers.insert(mt, {}); 0091 } 0092 it->push_back(handler); 0093 } 0094 0095 void BodyPartURLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler) 0096 { 0097 // don't delete them, only remove them from the list! 0098 auto it = mHandlers.begin(); 0099 while (it != mHandlers.end()) { 0100 it->erase(remove(it->begin(), it->end(), handler), it->end()); 0101 if (it->isEmpty()) { 0102 it = mHandlers.erase(it); 0103 } else { 0104 ++it; 0105 } 0106 } 0107 } 0108 0109 static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path) 0110 { 0111 Q_ASSERT(path); 0112 0113 if (!w || url.scheme() != QLatin1StringView("x-kmail")) { 0114 return nullptr; 0115 } 0116 const QString urlPath = url.path(); 0117 0118 // urlPath format is: /bodypart/<random number>/<part id>/<path> 0119 0120 qCDebug(MESSAGEVIEWER_LOG) << "BodyPartURLHandler: urlPath ==" << urlPath; 0121 if (!urlPath.startsWith(QLatin1StringView("/bodypart/"))) { 0122 return nullptr; 0123 } 0124 0125 const QStringList urlParts = urlPath.mid(10).split(QLatin1Char('/')); 0126 if (urlParts.size() != 3) { 0127 return nullptr; 0128 } 0129 // KMime::ContentIndex index( urlParts[1] ); 0130 QByteArray query(urlParts.at(2).toLatin1()); 0131 if (url.hasQuery()) { 0132 query += "?" + url.query().toLatin1(); 0133 } 0134 *path = QUrl::fromPercentEncoding(query); 0135 return w->nodeFromUrl(QUrl(urlParts.at(1))); 0136 } 0137 0138 QList<const Interface::BodyPartURLHandler *> BodyPartURLHandlerManager::handlersForPart(KMime::Content *node) const 0139 { 0140 if (auto ct = node->contentType(false)) { 0141 auto mimeType = ct->mimeType(); 0142 if (!mimeType.isEmpty()) { 0143 // Bug 390900 0144 if (mimeType == "text/x-vcard") { 0145 mimeType = "text/vcard"; 0146 } 0147 return mHandlers.value(mimeType); 0148 } 0149 } 0150 0151 return {}; 0152 } 0153 0154 bool BodyPartURLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const 0155 { 0156 QString path; 0157 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path); 0158 if (!node) { 0159 return false; 0160 } 0161 0162 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper()); 0163 0164 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) { 0165 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) { 0166 if ((*it)->handleClick(w->viewer(), &part, path)) { 0167 return true; 0168 } 0169 } 0170 } 0171 0172 return false; 0173 } 0174 0175 bool BodyPartURLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const 0176 { 0177 QString path; 0178 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path); 0179 if (!node) { 0180 return false; 0181 } 0182 0183 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper()); 0184 0185 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) { 0186 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) { 0187 if ((*it)->handleContextMenuRequest(&part, path, p)) { 0188 return true; 0189 } 0190 } 0191 } 0192 return false; 0193 } 0194 0195 QString BodyPartURLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const 0196 { 0197 QString path; 0198 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path); 0199 if (!node) { 0200 return {}; 0201 } 0202 0203 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper()); 0204 0205 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) { 0206 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) { 0207 const QString msg = (*it)->statusBarMessage(&part, path); 0208 if (!msg.isEmpty()) { 0209 return msg; 0210 } 0211 } 0212 } 0213 return {}; 0214 } 0215 0216 // 0217 // 0218 // URLHandlerManager 0219 // 0220 // 0221 0222 URLHandlerManager::URLHandlerManager() 0223 { 0224 registerHandler(new KMailProtocolURLHandler()); 0225 registerHandler(new ExpandCollapseQuoteURLManager()); 0226 registerHandler(new SMimeURLHandler()); 0227 registerHandler(new MailToURLHandler()); 0228 registerHandler(new ContactUidURLHandler()); 0229 registerHandler(new HtmlAnchorHandler()); 0230 registerHandler(new AttachmentURLHandler()); 0231 registerHandler(mBodyPartURLHandlerManager = new BodyPartURLHandlerManager()); 0232 registerHandler(new ShowAuditLogURLHandler()); 0233 registerHandler(new InternalImageURLHandler); 0234 registerHandler(new KRunURLHandler()); 0235 registerHandler(new EmbeddedImageURLHandler()); 0236 } 0237 0238 URLHandlerManager::~URLHandlerManager() 0239 { 0240 for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero<MimeTreeParser::URLHandler>()); 0241 } 0242 0243 URLHandlerManager *URLHandlerManager::instance() 0244 { 0245 if (!self) { 0246 self = new URLHandlerManager(); 0247 } 0248 return self; 0249 } 0250 0251 void URLHandlerManager::registerHandler(const MimeTreeParser::URLHandler *handler) 0252 { 0253 if (!handler) { 0254 return; 0255 } 0256 unregisterHandler(handler); // don't produce duplicates 0257 mHandlers.push_back(handler); 0258 } 0259 0260 void URLHandlerManager::unregisterHandler(const MimeTreeParser::URLHandler *handler) 0261 { 0262 // don't delete them, only remove them from the list! 0263 mHandlers.erase(remove(mHandlers.begin(), mHandlers.end(), handler), mHandlers.end()); 0264 } 0265 0266 void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType) 0267 { 0268 if (mBodyPartURLHandlerManager) { 0269 mBodyPartURLHandlerManager->registerHandler(handler, mimeType); 0270 } 0271 } 0272 0273 void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler) 0274 { 0275 if (mBodyPartURLHandlerManager) { 0276 mBodyPartURLHandlerManager->unregisterHandler(handler); 0277 } 0278 } 0279 0280 bool URLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const 0281 { 0282 HandlerList::const_iterator end(mHandlers.constEnd()); 0283 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { 0284 if ((*it)->handleClick(url, w)) { 0285 return true; 0286 } 0287 } 0288 return false; 0289 } 0290 0291 bool URLHandlerManager::handleShiftClick(const QUrl &url, ViewerPrivate *window) const 0292 { 0293 HandlerList::const_iterator end(mHandlers.constEnd()); 0294 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { 0295 if ((*it)->handleShiftClick(url, window)) { 0296 return true; 0297 } 0298 } 0299 return false; 0300 } 0301 0302 bool URLHandlerManager::willHandleDrag(const QUrl &url, ViewerPrivate *window) const 0303 { 0304 HandlerList::const_iterator end(mHandlers.constEnd()); 0305 0306 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { 0307 if ((*it)->willHandleDrag(url, window)) { 0308 return true; 0309 } 0310 } 0311 return false; 0312 } 0313 0314 bool URLHandlerManager::handleDrag(const QUrl &url, ViewerPrivate *window) const 0315 { 0316 HandlerList::const_iterator end(mHandlers.constEnd()); 0317 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { 0318 if ((*it)->handleDrag(url, window)) { 0319 return true; 0320 } 0321 } 0322 return false; 0323 } 0324 0325 bool URLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const 0326 { 0327 HandlerList::const_iterator end(mHandlers.constEnd()); 0328 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { 0329 if ((*it)->handleContextMenuRequest(url, p, w)) { 0330 return true; 0331 } 0332 } 0333 return false; 0334 } 0335 0336 QString URLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const 0337 { 0338 HandlerList::const_iterator end(mHandlers.constEnd()); 0339 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { 0340 const QString msg = (*it)->statusBarMessage(url, w); 0341 if (!msg.isEmpty()) { 0342 return msg; 0343 } 0344 } 0345 return {}; 0346 } 0347 0348 // 0349 // 0350 // URLHandler 0351 // 0352 // 0353 0354 bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const 0355 { 0356 if (url.scheme() == QLatin1StringView("kmail")) { 0357 if (!w) { 0358 return false; 0359 } 0360 const QString urlPath(url.path()); 0361 if (urlPath == QLatin1StringView("showHTML")) { 0362 w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html); 0363 w->update(MimeTreeParser::Force); 0364 return true; 0365 } else if (urlPath == QLatin1StringView("goOnline")) { 0366 w->goOnline(); 0367 return true; 0368 } else if (urlPath == QLatin1StringView("goResourceOnline")) { 0369 w->goResourceOnline(); 0370 return true; 0371 } else if (urlPath == QLatin1StringView("loadExternal")) { 0372 w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride()); 0373 w->update(MimeTreeParser::Force); 0374 return true; 0375 } else if (urlPath == QLatin1StringView("decryptMessage")) { 0376 w->setDecryptMessageOverwrite(true); 0377 w->update(MimeTreeParser::Force); 0378 return true; 0379 } else if (urlPath == QLatin1StringView("showSignatureDetails")) { 0380 w->setShowSignatureDetails(true); 0381 w->update(MimeTreeParser::Force); 0382 return true; 0383 } else if (urlPath == QLatin1StringView("hideSignatureDetails")) { 0384 w->setShowSignatureDetails(false); 0385 w->update(MimeTreeParser::Force); 0386 return true; 0387 } else if (urlPath == QLatin1StringView("showEncryptionDetails")) { 0388 w->setShowEncryptionDetails(true); 0389 w->update(MimeTreeParser::Force); 0390 return true; 0391 } else if (urlPath == QLatin1StringView("hideEncryptionDetails")) { 0392 w->setShowEncryptionDetails(false); 0393 w->update(MimeTreeParser::Force); 0394 return true; 0395 } 0396 } 0397 return false; 0398 } 0399 0400 QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const 0401 { 0402 const QString schemeStr = url.scheme(); 0403 if (schemeStr == QLatin1StringView("kmail")) { 0404 const QString urlPath(url.path()); 0405 if (urlPath == QLatin1StringView("showHTML")) { 0406 return i18n("Turn on HTML rendering for this message."); 0407 } else if (urlPath == QLatin1StringView("loadExternal")) { 0408 return i18n("Load external references from the Internet for this message."); 0409 } else if (urlPath == QLatin1StringView("goOnline")) { 0410 return i18n("Work online."); 0411 } else if (urlPath == QLatin1StringView("goResourceOnline")) { 0412 return i18n("Make account online."); 0413 } else if (urlPath == QLatin1StringView("decryptMessage")) { 0414 return i18n("Decrypt message."); 0415 } else if (urlPath == QLatin1StringView("showSignatureDetails")) { 0416 return i18n("Show signature details."); 0417 } else if (urlPath == QLatin1StringView("hideSignatureDetails")) { 0418 return i18n("Hide signature details."); 0419 } else if (urlPath == QLatin1StringView("showEncryptionDetails")) { 0420 return i18n("Show encryption details."); 0421 } else if (urlPath == QLatin1StringView("hideEncryptionDetails")) { 0422 return i18n("Hide encryption details."); 0423 } else { 0424 return {}; 0425 } 0426 } else if (schemeStr == QLatin1StringView("help")) { 0427 return i18n("Open Documentation"); 0428 } 0429 return {}; 0430 } 0431 0432 bool ExpandCollapseQuoteURLManager::handleClick(const QUrl &url, ViewerPrivate *w) const 0433 { 0434 // kmail:levelquote/?num -> the level quote to collapse. 0435 // kmail:levelquote/?-num -> expand all levels quote. 0436 if (url.scheme() == QLatin1StringView("kmail") && url.path() == QLatin1StringView("levelquote")) { 0437 const QString levelStr = url.query(); 0438 bool isNumber = false; 0439 const int levelQuote = levelStr.toInt(&isNumber); 0440 if (isNumber) { 0441 w->slotLevelQuote(levelQuote); 0442 } 0443 return true; 0444 } 0445 return false; 0446 } 0447 0448 bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const 0449 { 0450 Q_UNUSED(url) 0451 Q_UNUSED(window) 0452 return false; 0453 } 0454 0455 QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const 0456 { 0457 if (url.scheme() == QLatin1StringView("kmail") && url.path() == QLatin1StringView("levelquote")) { 0458 const QString query = url.query(); 0459 if (query.length() >= 1) { 0460 if (query[0] == QLatin1Char('-')) { 0461 return i18n("Expand all quoted text."); 0462 } else { 0463 return i18n("Collapse quoted text."); 0464 } 0465 } 0466 } 0467 return {}; 0468 } 0469 0470 bool foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId) 0471 { 0472 static QString showCertMan(QStringLiteral("showCertificate#")); 0473 displayName.clear(); 0474 libName.clear(); 0475 keyId.clear(); 0476 int i1 = aUrl.indexOf(showCertMan); 0477 if (-1 < i1) { 0478 i1 += showCertMan.length(); 0479 int i2 = aUrl.indexOf(QLatin1StringView(" ### "), i1); 0480 if (i1 < i2) { 0481 displayName = aUrl.mid(i1, i2 - i1); 0482 i1 = i2 + 5; 0483 i2 = aUrl.indexOf(QLatin1StringView(" ### "), i1); 0484 if (i1 < i2) { 0485 libName = aUrl.mid(i1, i2 - i1); 0486 i2 += 5; 0487 0488 keyId = aUrl.mid(i2); 0489 /* 0490 int len = aUrl.length(); 0491 if( len > i2+1 ) { 0492 keyId = aUrl.mid( i2, 2 ); 0493 i2 += 2; 0494 while( len > i2+1 ) { 0495 keyId += ':'; 0496 keyId += aUrl.mid( i2, 2 ); 0497 i2 += 2; 0498 } 0499 } 0500 */ 0501 } 0502 } 0503 } 0504 return !keyId.isEmpty(); 0505 } 0506 0507 bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const 0508 { 0509 if (!url.hasFragment()) { 0510 return false; 0511 } 0512 QString displayName; 0513 QString libName; 0514 QString keyId; 0515 if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) { 0516 return false; 0517 } 0518 QStringList lst; 0519 lst << QStringLiteral("--parent-windowid") << QString::number(static_cast<qlonglong>(w->viewer()->mainWindow()->winId())) << QStringLiteral("--query") 0520 << keyId; 0521 #ifdef Q_OS_WIN 0522 QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra.exe"), {QCoreApplication::applicationDirPath()}); 0523 if (exec.isEmpty()) { 0524 exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra.exe")); 0525 } 0526 #else 0527 const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); 0528 #endif 0529 if (exec.isEmpty()) { 0530 qCWarning(MESSAGEVIEWER_LOG) << "Could not find kleopatra executable in PATH"; 0531 KMessageBox::error(w->mMainWindow, 0532 i18n("Could not start certificate manager. " 0533 "Please check your installation."), 0534 i18n("KMail Error")); 0535 return false; 0536 } 0537 QProcess::startDetached(exec, lst); 0538 return true; 0539 } 0540 0541 QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const 0542 { 0543 QString displayName; 0544 QString libName; 0545 QString keyId; 0546 if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) { 0547 return {}; 0548 } 0549 return i18n("Show certificate 0x%1", keyId); 0550 } 0551 0552 bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const 0553 { 0554 if (!url.host().isEmpty() || !url.hasFragment()) { 0555 return false; 0556 } 0557 0558 w->scrollToAnchor(url.fragment()); 0559 return true; 0560 } 0561 0562 QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const 0563 { 0564 if (url.scheme() == QLatin1StringView("mailto")) { 0565 return KEmailAddress::decodeMailtoUrl(url); 0566 } 0567 return {}; 0568 } 0569 0570 static QString searchFullEmailByUid(const QString &uid) 0571 { 0572 QString fullEmail; 0573 auto job = new Akonadi::ContactSearchJob(); 0574 job->setLimit(1); 0575 job->setQuery(Akonadi::ContactSearchJob::ContactUid, uid, Akonadi::ContactSearchJob::ExactMatch); 0576 job->exec(); 0577 const KContacts::Addressee::List res = job->contacts(); 0578 if (!res.isEmpty()) { 0579 KContacts::Addressee addr = res.at(0); 0580 fullEmail = addr.fullEmail(); 0581 } 0582 return fullEmail; 0583 } 0584 0585 static void runKAddressBook(const QUrl &url) 0586 { 0587 auto job = new Akonadi::OpenEmailAddressJob(url.path(), nullptr); 0588 job->start(); 0589 } 0590 0591 bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const 0592 { 0593 if (url.scheme() == QLatin1StringView("uid")) { 0594 runKAddressBook(url); 0595 return true; 0596 } else { 0597 return false; 0598 } 0599 } 0600 0601 bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const 0602 { 0603 if (url.scheme() != QLatin1StringView("uid") || url.path().isEmpty()) { 0604 return false; 0605 } 0606 0607 QMenu menu; 0608 QAction *open = menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book")); 0609 #ifndef QT_NO_CLIPBOARD 0610 QAction *copy = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address")); 0611 #endif 0612 0613 QAction *a = menu.exec(p); 0614 if (a == open) { 0615 runKAddressBook(url); 0616 #ifndef QT_NO_CLIPBOARD 0617 } else if (a == copy) { 0618 const QString fullEmail = searchFullEmailByUid(url.path()); 0619 if (!fullEmail.isEmpty()) { 0620 QClipboard *clip = QApplication::clipboard(); 0621 clip->setText(fullEmail, QClipboard::Clipboard); 0622 clip->setText(fullEmail, QClipboard::Selection); 0623 PimCommon::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard.")); 0624 } 0625 #endif 0626 } 0627 0628 return true; 0629 } 0630 0631 QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const 0632 { 0633 if (url.scheme() == QLatin1StringView("uid")) { 0634 return i18n("Lookup the contact in KAddressbook"); 0635 } else { 0636 return {}; 0637 } 0638 } 0639 0640 KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const 0641 { 0642 if (!w || !w->mMessage) { 0643 return nullptr; 0644 } 0645 if (url.scheme() == QLatin1StringView("attachment")) { 0646 KMime::Content *node = w->nodeFromUrl(url); 0647 return node; 0648 } 0649 return nullptr; 0650 } 0651 0652 bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const 0653 { 0654 bool inHeader = false; 0655 QUrlQuery query(url); 0656 const QString place = query.queryItemValue(QStringLiteral("place")).toLower(); 0657 if (!place.isNull()) { 0658 inHeader = (place == QLatin1StringView("header")); 0659 } 0660 return inHeader; 0661 } 0662 0663 bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const 0664 { 0665 KMime::Content *node = nodeForUrl(url, w); 0666 if (!node) { 0667 return false; 0668 } 0669 const bool inHeader = attachmentIsInHeader(url); 0670 const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader; 0671 if (inHeader) { 0672 w->scrollToAttachment(node); 0673 } 0674 // if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) { 0675 w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node)); 0676 //} 0677 0678 return true; 0679 } 0680 0681 bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const 0682 { 0683 KMime::Content *node = nodeForUrl(url, window); 0684 if (!node) { 0685 return false; 0686 } 0687 if (!window) { 0688 return false; 0689 } 0690 if (node->contentType()->mimeType() == "text/x-moz-deleted") { 0691 return false; 0692 } 0693 0694 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); 0695 if (isEncapsulatedMessage) { 0696 KMime::Message::Ptr message(new KMime::Message); 0697 message->setContent(node->parent()->bodyAsMessage()->encodedContent()); 0698 message->parse(); 0699 Akonadi::Item item; 0700 item.setPayload<KMime::Message::Ptr>(message); 0701 Akonadi::MessageFlags::copyMessageFlags(*message, item); 0702 item.setMimeType(KMime::Message::mimeType()); 0703 QUrl newUrl; 0704 if (MessageViewer::Util::saveMessageInMboxAndGetUrl(newUrl, Akonadi::Item::List() << item, window->viewer())) { 0705 window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl); 0706 } 0707 } else { 0708 QList<QUrl> urlList; 0709 if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) { 0710 window->viewer()->showOpenAttachmentFolderWidget(urlList); 0711 } 0712 } 0713 0714 return true; 0715 } 0716 0717 bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const 0718 { 0719 return nodeForUrl(url, window) != nullptr; 0720 } 0721 0722 bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const 0723 { 0724 #ifndef QT_NO_DRAGANDDROP 0725 KMime::Content *node = nodeForUrl(url, window); 0726 if (!node) { 0727 return false; 0728 } 0729 if (node->contentType()->mimeType() == "text/x-moz-deleted") { 0730 return false; 0731 } 0732 QString fileName; 0733 QUrl tUrl; 0734 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); 0735 if (isEncapsulatedMessage) { 0736 KMime::Message::Ptr message(new KMime::Message); 0737 message->setContent(node->parent()->bodyAsMessage()->encodedContent()); 0738 message->parse(); 0739 Akonadi::Item item; 0740 item.setPayload<KMime::Message::Ptr>(message); 0741 Akonadi::MessageFlags::copyMessageFlags(*message, item); 0742 item.setMimeType(KMime::Message::mimeType()); 0743 fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item)); 0744 0745 KMBox::MBox mbox; 0746 QFile::remove(fileName); 0747 0748 if (!mbox.load(fileName)) { 0749 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file"; 0750 return false; 0751 } 0752 mbox.appendMessage(item.payload<KMime::Message::Ptr>()); 0753 0754 if (!mbox.save()) { 0755 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file"; 0756 return false; 0757 } 0758 tUrl = QUrl::fromLocalFile(fileName); 0759 } else { 0760 if (node->header<KMime::Headers::Subject>()) { 0761 if (!node->contents().isEmpty()) { 0762 node = node->contents().constLast(); 0763 fileName = window->nodeHelper()->writeNodeToTempFile(node); 0764 tUrl = QUrl::fromLocalFile(fileName); 0765 } 0766 } 0767 if (fileName.isEmpty()) { 0768 tUrl = window->nodeHelper()->tempFileUrlFromNode(node); 0769 fileName = tUrl.path(); 0770 } 0771 } 0772 if (!fileName.isEmpty()) { 0773 QFile f(fileName); 0774 f.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther); 0775 const QString icon = Util::iconPathForContent(node, KIconLoader::Small); 0776 auto drag = new QDrag(window->viewer()); 0777 auto mimeData = new QMimeData(); 0778 mimeData->setUrls(QList<QUrl>() << tUrl); 0779 drag->setMimeData(mimeData); 0780 if (!icon.isEmpty()) { 0781 drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16)); 0782 } 0783 drag->exec(); 0784 return true; 0785 } else { 0786 #endif 0787 return false; 0788 } 0789 } 0790 0791 bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const 0792 { 0793 KMime::Content *node = nodeForUrl(url, w); 0794 if (!node) { 0795 return false; 0796 } 0797 // PENDING(romain_kdab) : replace with toLocalFile() ? 0798 w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p); 0799 return true; 0800 } 0801 0802 QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const 0803 { 0804 KMime::Content *node = nodeForUrl(url, w); 0805 if (!node) { 0806 return {}; 0807 } 0808 const QString name = MimeTreeParser::NodeHelper::fileName(node); 0809 if (!name.isEmpty()) { 0810 return i18n("Attachment: %1", name); 0811 } else if (dynamic_cast<KMime::Message *>(node)) { 0812 if (node->header<KMime::Headers::Subject>()) { 0813 return i18n("Encapsulated Message (Subject: %1)", node->header<KMime::Headers::Subject>()->asUnicodeString()); 0814 } else { 0815 return i18n("Encapsulated Message"); 0816 } 0817 } 0818 return i18n("Unnamed attachment"); 0819 } 0820 0821 static QString extractAuditLog(const QUrl &url) 0822 { 0823 if (url.scheme() != QLatin1StringView("kmail") || url.path() != QLatin1StringView("showAuditLog")) { 0824 return {}; 0825 } 0826 QUrlQuery query(url); 0827 Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty()); 0828 return query.queryItemValue(QStringLiteral("log")); 0829 } 0830 0831 bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const 0832 { 0833 const QString auditLog = extractAuditLog(url); 0834 if (auditLog.isEmpty()) { 0835 return false; 0836 } 0837 Kleo::AuditLogViewer::showAuditLog(w->mMainWindow, Kleo::AuditLogEntry{auditLog, GpgME::Error{}}, auditLog); 0838 return true; 0839 } 0840 0841 bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const 0842 { 0843 Q_UNUSED(w) 0844 // disable RMB for my own links: 0845 return !extractAuditLog(url).isEmpty(); 0846 } 0847 0848 QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const 0849 { 0850 if (extractAuditLog(url).isEmpty()) { 0851 return {}; 0852 } else { 0853 return i18n("Show GnuPG Audit Log for this operation"); 0854 } 0855 } 0856 0857 bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const 0858 { 0859 Q_UNUSED(url) 0860 Q_UNUSED(window) 0861 return true; 0862 } 0863 0864 bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const 0865 { 0866 Q_UNUSED(window) 0867 Q_UNUSED(url) 0868 0869 // This will only be called when willHandleDrag() was true. Return false here, that will 0870 // notify ViewerPrivate::eventFilter() that no drag was started. 0871 return false; 0872 } 0873 0874 bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const 0875 { 0876 Q_UNUSED(window) 0877 if (url.scheme() == QLatin1StringView("data") && url.path().startsWith(QLatin1StringView("image"))) { 0878 return true; 0879 } 0880 0881 const QString imagePath = 0882 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libmessageviewer/pics/"), QStandardPaths::LocateDirectory); 0883 return url.path().contains(imagePath); 0884 } 0885 0886 bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const 0887 { 0888 const QString scheme(url.scheme()); 0889 if ((scheme == QLatin1StringView("http")) || (scheme == QLatin1StringView("https")) || (scheme == QLatin1StringView("ftp")) 0890 || (scheme == QLatin1StringView("file")) || (scheme == QLatin1StringView("ftps")) || (scheme == QLatin1StringView("sftp")) 0891 || (scheme == QLatin1StringView("help")) || (scheme == QLatin1StringView("vnc")) || (scheme == QLatin1StringView("smb")) 0892 || (scheme == QLatin1StringView("fish")) || (scheme == QLatin1StringView("news")) || (scheme == QLatin1StringView("tel")) 0893 || (scheme == QLatin1StringView("geo")) || (scheme == QLatin1StringView("sms"))) { 0894 PimCommon::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL...")); 0895 QTimer::singleShot(2s, PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::reset); 0896 0897 QMimeDatabase mimeDb; 0898 auto mime = mimeDb.mimeTypeForUrl(url); 0899 if (mime.name() == QLatin1StringView("application/x-desktop") || mime.name() == QLatin1StringView("application/x-executable") 0900 || mime.name() == QLatin1StringView("application/x-ms-dos-executable") || mime.name() == QLatin1StringView("application/x-shellscript")) { 0901 if (KMessageBox::warningTwoActions( 0902 nullptr, 0903 xi18nc("@info", "Do you really want to execute <filename>%1</filename>?", url.toDisplayString(QUrl::PreferLocalFile)), 0904 QString(), 0905 KGuiItem(i18n("Execute")), 0906 KStandardGuiItem::cancel()) 0907 != KMessageBox::ButtonCode::PrimaryAction) { 0908 return true; 0909 } 0910 } 0911 w->checkPhishingUrl(); 0912 return true; 0913 } else { 0914 return false; 0915 } 0916 } 0917 0918 bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const 0919 { 0920 Q_UNUSED(url) 0921 Q_UNUSED(window) 0922 return false; 0923 } 0924 0925 bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const 0926 { 0927 Q_UNUSED(window) 0928 return url.scheme() == QLatin1StringView("cid"); 0929 }