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 }