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/Contact/OpenEmailAddressJob>
26 #include <MessageCore/StringUtil>
27 #include <PimCommon/BroadcastStatus>
28 
29 #include <Akonadi/Contact/ContactSearchJob>
30 
31 #include "messageflags.h"
32 #include <KEmailAddress>
33 #include <KMbox/MBox>
34 #include <KMime/Content>
35 
36 #include <KIconLoader>
37 #include <KLocalizedString>
38 #include <KMessageBox>
39 
40 #include <QApplication>
41 #include <QClipboard>
42 #include <QDrag>
43 #include <QFile>
44 #include <QIcon>
45 #include <QMenu>
46 #include <QMimeData>
47 #include <QMimeDatabase>
48 #include <QProcess>
49 #include <QStandardPaths>
50 #include <QUrl>
51 #include <QUrlQuery>
52 
53 #include <algorithm>
54 
55 #include <Libkleo/MessageBox>
56 #include <chrono>
57 
58 using namespace std::chrono_literals;
59 
60 using std::find;
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 
~BodyPartURLHandlerManager()74 BodyPartURLHandlerManager::~BodyPartURLHandlerManager()
75 {
76     for_each(mHandlers.begin(), mHandlers.end(), [](QVector<const Interface::BodyPartURLHandler *> &handlers) {
77         for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero<Interface::BodyPartURLHandler>());
78     });
79 }
80 
registerHandler(const Interface::BodyPartURLHandler * handler,const QString & mimeType)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 
unregisterHandler(const Interface::BodyPartURLHandler * handler)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 
partNodeFromXKMailUrl(const QUrl & url,ViewerPrivate * w,QString * path)109 static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path)
110 {
111     Q_ASSERT(path);
112 
113     if (!w || url.scheme() != QLatin1String("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(QLatin1String("/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     }
134     *path = QUrl::fromPercentEncoding(query);
135     return w->nodeFromUrl(QUrl(urlParts.at(1)));
136 }
137 
handlersForPart(KMime::Content * node) const138 QVector<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 
handleClick(const QUrl & url,ViewerPrivate * w) const154 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 
handleContextMenuRequest(const QUrl & url,const QPoint & p,ViewerPrivate * w) const175 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 
statusBarMessage(const QUrl & url,ViewerPrivate * w) const195 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 QString();
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 QString();
214 }
215 
216 //
217 //
218 // URLHandlerManager
219 //
220 //
221 
URLHandlerManager()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 
~URLHandlerManager()238 URLHandlerManager::~URLHandlerManager()
239 {
240     for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero<MimeTreeParser::URLHandler>());
241 }
242 
instance()243 URLHandlerManager *URLHandlerManager::instance()
244 {
245     if (!self) {
246         self = new URLHandlerManager();
247     }
248     return self;
249 }
250 
registerHandler(const MimeTreeParser::URLHandler * handler)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 
unregisterHandler(const MimeTreeParser::URLHandler * handler)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 
registerHandler(const Interface::BodyPartURLHandler * handler,const QString & mimeType)266 void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
267 {
268     if (mBodyPartURLHandlerManager) {
269         mBodyPartURLHandlerManager->registerHandler(handler, mimeType);
270     }
271 }
272 
unregisterHandler(const Interface::BodyPartURLHandler * handler)273 void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
274 {
275     if (mBodyPartURLHandlerManager) {
276         mBodyPartURLHandlerManager->unregisterHandler(handler);
277     }
278 }
279 
handleClick(const QUrl & url,ViewerPrivate * w) const280 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 
handleShiftClick(const QUrl & url,ViewerPrivate * window) const291 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 
willHandleDrag(const QUrl & url,ViewerPrivate * window) const302 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 
handleDrag(const QUrl & url,ViewerPrivate * window) const314 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 
handleContextMenuRequest(const QUrl & url,const QPoint & p,ViewerPrivate * w) const325 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 
statusBarMessage(const QUrl & url,ViewerPrivate * w) const336 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 QString();
346 }
347 
348 //
349 //
350 // URLHandler
351 //
352 //
353 
handleClick(const QUrl & url,ViewerPrivate * w) const354 bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
355 {
356     if (url.scheme() == QLatin1String("kmail")) {
357         if (!w) {
358             return false;
359         }
360         const QString urlPath(url.path());
361         if (urlPath == QLatin1String("showHTML")) {
362             w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html);
363             w->update(MimeTreeParser::Force);
364             return true;
365         } else if (urlPath == QLatin1String("goOnline")) {
366             w->goOnline();
367             return true;
368         } else if (urlPath == QLatin1String("goResourceOnline")) {
369             w->goResourceOnline();
370             return true;
371         } else if (urlPath == QLatin1String("loadExternal")) {
372             w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride());
373             w->update(MimeTreeParser::Force);
374             return true;
375         } else if (urlPath == QLatin1String("decryptMessage")) {
376             w->setDecryptMessageOverwrite(true);
377             w->update(MimeTreeParser::Force);
378             return true;
379         } else if (urlPath == QLatin1String("showSignatureDetails")) {
380             w->setShowSignatureDetails(true);
381             w->update(MimeTreeParser::Force);
382             return true;
383         } else if (urlPath == QLatin1String("hideSignatureDetails")) {
384             w->setShowSignatureDetails(false);
385             w->update(MimeTreeParser::Force);
386             return true;
387         } else if (urlPath == QLatin1String("showEncryptionDetails")) {
388             w->setShowEncryptionDetails(true);
389             w->update(MimeTreeParser::Force);
390             return true;
391         } else if (urlPath == QLatin1String("hideEncryptionDetails")) {
392             w->setShowEncryptionDetails(false);
393             w->update(MimeTreeParser::Force);
394             return true;
395         }
396     }
397     return false;
398 }
399 
statusBarMessage(const QUrl & url,ViewerPrivate *) const400 QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
401 {
402     const QString schemeStr = url.scheme();
403     if (schemeStr == QLatin1String("kmail")) {
404         const QString urlPath(url.path());
405         if (urlPath == QLatin1String("showHTML")) {
406             return i18n("Turn on HTML rendering for this message.");
407         } else if (urlPath == QLatin1String("loadExternal")) {
408             return i18n("Load external references from the Internet for this message.");
409         } else if (urlPath == QLatin1String("goOnline")) {
410             return i18n("Work online.");
411         } else if (urlPath == QLatin1String("goResourceOnline")) {
412             return i18n("Make account online.");
413         } else if (urlPath == QLatin1String("decryptMessage")) {
414             return i18n("Decrypt message.");
415         } else if (urlPath == QLatin1String("showSignatureDetails")) {
416             return i18n("Show signature details.");
417         } else if (urlPath == QLatin1String("hideSignatureDetails")) {
418             return i18n("Hide signature details.");
419         } else if (urlPath == QLatin1String("showEncryptionDetails")) {
420             return i18n("Show encryption details.");
421         } else if (urlPath == QLatin1String("hideEncryptionDetails")) {
422             return i18n("Hide encryption details.");
423         } else {
424             return QString();
425         }
426     } else if (schemeStr == QLatin1String("help")) {
427         return i18n("Open Documentation");
428     }
429     return QString();
430 }
431 
handleClick(const QUrl & url,ViewerPrivate * w) const432 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() == QLatin1String("kmail") && url.path() == QLatin1String("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 
handleDrag(const QUrl & url,ViewerPrivate * window) const448 bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
449 {
450     Q_UNUSED(url)
451     Q_UNUSED(window)
452     return false;
453 }
454 
statusBarMessage(const QUrl & url,ViewerPrivate *) const455 QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const
456 {
457     if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("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 QString();
468 }
469 
foundSMIMEData(const QString & aUrl,QString & displayName,QString & libName,QString & keyId)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(QLatin1String(" ### "), i1);
480         if (i1 < i2) {
481             displayName = aUrl.mid(i1, i2 - i1);
482             i1 = i2 + 5;
483             i2 = aUrl.indexOf(QLatin1String(" ### "), 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 
handleClick(const QUrl & url,ViewerPrivate * w) const507 bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
508 {
509     if (!url.hasFragment()) {
510         return false;
511     }
512     QString displayName;
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     if (!QProcess::startDetached(QStringLiteral("kleopatra"), lst)) {
522         KMessageBox::error(w->mMainWindow,
523                            i18n("Could not start certificate manager. "
524                                 "Please check your installation."),
525                            i18n("KMail Error"));
526     }
527     return true;
528 }
529 
statusBarMessage(const QUrl & url,ViewerPrivate *) const530 QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
531 {
532     QString displayName;
533     QString libName;
534     QString keyId;
535     if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) {
536         return QString();
537     }
538     return i18n("Show certificate 0x%1", keyId);
539 }
540 
handleClick(const QUrl & url,ViewerPrivate * w) const541 bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
542 {
543     if (!url.host().isEmpty() || !url.hasFragment()) {
544         return false;
545     }
546 
547     w->scrollToAnchor(url.fragment());
548     return true;
549 }
550 
statusBarMessage(const QUrl & url,ViewerPrivate *) const551 QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
552 {
553     if (url.scheme() == QLatin1String("mailto")) {
554         return KEmailAddress::decodeMailtoUrl(url);
555     }
556     return QString();
557 }
558 
searchFullEmailByUid(const QString & uid)559 static QString searchFullEmailByUid(const QString &uid)
560 {
561     QString fullEmail;
562     auto job = new Akonadi::ContactSearchJob();
563     job->setLimit(1);
564     job->setQuery(Akonadi::ContactSearchJob::ContactUid, uid, Akonadi::ContactSearchJob::ExactMatch);
565     job->exec();
566     const KContacts::Addressee::List res = job->contacts();
567     if (!res.isEmpty()) {
568         KContacts::Addressee addr = res.at(0);
569         fullEmail = addr.fullEmail();
570     }
571     return fullEmail;
572 }
573 
runKAddressBook(const QUrl & url)574 static void runKAddressBook(const QUrl &url)
575 {
576     auto job = new Akonadi::OpenEmailAddressJob(url.path(), nullptr);
577     job->start();
578 }
579 
handleClick(const QUrl & url,ViewerPrivate *) const580 bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const
581 {
582     if (url.scheme() == QLatin1String("uid")) {
583         runKAddressBook(url);
584         return true;
585     } else {
586         return false;
587     }
588 }
589 
handleContextMenuRequest(const QUrl & url,const QPoint & p,ViewerPrivate *) const590 bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const
591 {
592     if (url.scheme() != QLatin1String("uid") || url.path().isEmpty()) {
593         return false;
594     }
595 
596     QMenu menu;
597     QAction *open = menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book"));
598 #ifndef QT_NO_CLIPBOARD
599     QAction *copy = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address"));
600 #endif
601 
602     QAction *a = menu.exec(p);
603     if (a == open) {
604         runKAddressBook(url);
605 #ifndef QT_NO_CLIPBOARD
606     } else if (a == copy) {
607         const QString fullEmail = searchFullEmailByUid(url.path());
608         if (!fullEmail.isEmpty()) {
609             QClipboard *clip = QApplication::clipboard();
610             clip->setText(fullEmail, QClipboard::Clipboard);
611             clip->setText(fullEmail, QClipboard::Selection);
612             PimCommon::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
613         }
614 #endif
615     }
616 
617     return true;
618 }
619 
statusBarMessage(const QUrl & url,ViewerPrivate *) const620 QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
621 {
622     if (url.scheme() == QLatin1String("uid")) {
623         return i18n("Lookup the contact in KAddressbook");
624     } else {
625         return QString();
626     }
627 }
628 
nodeForUrl(const QUrl & url,ViewerPrivate * w) const629 KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const
630 {
631     if (!w || !w->mMessage) {
632         return nullptr;
633     }
634     if (url.scheme() == QLatin1String("attachment")) {
635         KMime::Content *node = w->nodeFromUrl(url);
636         return node;
637     }
638     return nullptr;
639 }
640 
attachmentIsInHeader(const QUrl & url) const641 bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const
642 {
643     bool inHeader = false;
644     QUrlQuery query(url);
645     const QString place = query.queryItemValue(QStringLiteral("place")).toLower();
646     if (!place.isNull()) {
647         inHeader = (place == QLatin1String("header"));
648     }
649     return inHeader;
650 }
651 
handleClick(const QUrl & url,ViewerPrivate * w) const652 bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
653 {
654     KMime::Content *node = nodeForUrl(url, w);
655     if (!node) {
656         return false;
657     }
658     const bool inHeader = attachmentIsInHeader(url);
659     const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
660     if (inHeader) {
661         w->scrollToAttachment(node);
662     }
663     // if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
664     w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
665     //}
666 
667     return true;
668 }
669 
handleShiftClick(const QUrl & url,ViewerPrivate * window) const670 bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
671 {
672     KMime::Content *node = nodeForUrl(url, window);
673     if (!node) {
674         return false;
675     }
676     if (!window) {
677         return false;
678     }
679     if (node->contentType()->mimeType() == "text/x-moz-deleted") {
680         return false;
681     }
682 
683     const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
684     if (isEncapsulatedMessage) {
685         KMime::Message::Ptr message(new KMime::Message);
686         message->setContent(node->parent()->bodyAsMessage()->encodedContent());
687         message->parse();
688         Akonadi::Item item;
689         item.setPayload<KMime::Message::Ptr>(message);
690         Akonadi::MessageFlags::copyMessageFlags(*message, item);
691         item.setMimeType(KMime::Message::mimeType());
692         QUrl newUrl;
693         if (MessageViewer::Util::saveMessageInMboxAndGetUrl(newUrl, Akonadi::Item::List() << item, window->viewer())) {
694             window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl);
695         }
696     } else {
697         QList<QUrl> urlList;
698         if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) {
699             window->viewer()->showOpenAttachmentFolderWidget(urlList);
700         }
701     }
702 
703     return true;
704 }
705 
willHandleDrag(const QUrl & url,ViewerPrivate * window) const706 bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
707 {
708     return nodeForUrl(url, window) != nullptr;
709 }
710 
handleDrag(const QUrl & url,ViewerPrivate * window) const711 bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
712 {
713 #ifndef QT_NO_DRAGANDDROP
714     KMime::Content *node = nodeForUrl(url, window);
715     if (!node) {
716         return false;
717     }
718     if (node->contentType()->mimeType() == "text/x-moz-deleted") {
719         return false;
720     }
721     QString fileName;
722     QUrl tUrl;
723     const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
724     if (isEncapsulatedMessage) {
725         KMime::Message::Ptr message(new KMime::Message);
726         message->setContent(node->parent()->bodyAsMessage()->encodedContent());
727         message->parse();
728         Akonadi::Item item;
729         item.setPayload<KMime::Message::Ptr>(message);
730         Akonadi::MessageFlags::copyMessageFlags(*message, item);
731         item.setMimeType(KMime::Message::mimeType());
732         fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
733 
734         KMBox::MBox mbox;
735         QFile::remove(fileName);
736 
737         if (!mbox.load(fileName)) {
738             qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file";
739             return false;
740         }
741         mbox.appendMessage(item.payload<KMime::Message::Ptr>());
742 
743         if (!mbox.save()) {
744             qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file";
745             return false;
746         }
747         tUrl = QUrl::fromLocalFile(fileName);
748     } else {
749         if (node->header<KMime::Headers::Subject>()) {
750             if (!node->contents().isEmpty()) {
751                 node = node->contents().constLast();
752                 fileName = window->nodeHelper()->writeNodeToTempFile(node);
753                 tUrl = QUrl::fromLocalFile(fileName);
754             }
755         }
756         if (fileName.isEmpty()) {
757             tUrl = window->nodeHelper()->tempFileUrlFromNode(node);
758             fileName = tUrl.path();
759         }
760     }
761     if (!fileName.isEmpty()) {
762         QFile f(fileName);
763         f.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther);
764         const QString icon = Util::iconPathForContent(node, KIconLoader::Small);
765         auto drag = new QDrag(window->viewer());
766         auto mimeData = new QMimeData();
767         mimeData->setUrls(QList<QUrl>() << tUrl);
768         drag->setMimeData(mimeData);
769         if (!icon.isEmpty()) {
770             drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
771         }
772         drag->exec();
773         return true;
774     } else {
775 #endif
776         return false;
777 }
778 }
779 
handleContextMenuRequest(const QUrl & url,const QPoint & p,ViewerPrivate * w) const780 bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
781 {
782     KMime::Content *node = nodeForUrl(url, w);
783     if (!node) {
784         return false;
785     }
786     // PENDING(romain_kdab) : replace with toLocalFile() ?
787     w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
788     return true;
789 }
790 
statusBarMessage(const QUrl & url,ViewerPrivate * w) const791 QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
792 {
793     KMime::Content *node = nodeForUrl(url, w);
794     if (!node) {
795         return QString();
796     }
797     const QString name = MimeTreeParser::NodeHelper::fileName(node);
798     if (!name.isEmpty()) {
799         return i18n("Attachment: %1", name);
800     } else if (dynamic_cast<KMime::Message *>(node)) {
801         if (node->header<KMime::Headers::Subject>()) {
802             return i18n("Encapsulated Message (Subject: %1)", node->header<KMime::Headers::Subject>()->asUnicodeString());
803         } else {
804             return i18n("Encapsulated Message");
805         }
806     }
807     return i18n("Unnamed attachment");
808 }
809 
extractAuditLog(const QUrl & url)810 static QString extractAuditLog(const QUrl &url)
811 {
812     if (url.scheme() != QLatin1String("kmail") || url.path() != QLatin1String("showAuditLog")) {
813         return QString();
814     }
815     QUrlQuery query(url);
816     Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty());
817     return query.queryItemValue(QStringLiteral("log"));
818 }
819 
handleClick(const QUrl & url,ViewerPrivate * w) const820 bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
821 {
822     const QString auditLog = extractAuditLog(url);
823     if (auditLog.isEmpty()) {
824         return false;
825     }
826     Kleo::MessageBox::auditLog(w->mMainWindow, auditLog);
827     return true;
828 }
829 
handleContextMenuRequest(const QUrl & url,const QPoint &,ViewerPrivate * w) const830 bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const
831 {
832     Q_UNUSED(w)
833     // disable RMB for my own links:
834     return !extractAuditLog(url).isEmpty();
835 }
836 
statusBarMessage(const QUrl & url,ViewerPrivate *) const837 QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
838 {
839     if (extractAuditLog(url).isEmpty()) {
840         return QString();
841     } else {
842         return i18n("Show GnuPG Audit Log for this operation");
843     }
844 }
845 
handleDrag(const QUrl & url,ViewerPrivate * window) const846 bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
847 {
848     Q_UNUSED(url)
849     Q_UNUSED(window)
850     return true;
851 }
852 
handleDrag(const QUrl & url,ViewerPrivate * window) const853 bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
854 {
855     Q_UNUSED(window)
856     Q_UNUSED(url)
857 
858     // This will only be called when willHandleDrag() was true. Return false here, that will
859     // notify ViewerPrivate::eventFilter() that no drag was started.
860     return false;
861 }
862 
willHandleDrag(const QUrl & url,ViewerPrivate * window) const863 bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
864 {
865     Q_UNUSED(window)
866     if (url.scheme() == QLatin1String("data") && url.path().startsWith(QLatin1String("image"))) {
867         return true;
868     }
869 
870     const QString imagePath =
871         QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libmessageviewer/pics/"), QStandardPaths::LocateDirectory);
872     return url.path().contains(imagePath);
873 }
874 
handleClick(const QUrl & url,ViewerPrivate * w) const875 bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
876 {
877     const QString scheme(url.scheme());
878     if ((scheme == QLatin1String("http")) || (scheme == QLatin1String("https")) || (scheme == QLatin1String("ftp")) || (scheme == QLatin1String("file"))
879         || (scheme == QLatin1String("ftps")) || (scheme == QLatin1String("sftp")) || (scheme == QLatin1String("help")) || (scheme == QLatin1String("vnc"))
880         || (scheme == QLatin1String("smb")) || (scheme == QLatin1String("fish")) || (scheme == QLatin1String("news")) || (scheme == QLatin1String("tel"))) {
881         PimCommon::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL..."));
882         QTimer::singleShot(2s, PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::reset);
883 
884         QMimeDatabase mimeDb;
885         auto mime = mimeDb.mimeTypeForUrl(url);
886         if (mime.name() == QLatin1String("application/x-desktop") || mime.name() == QLatin1String("application/x-executable")
887             || mime.name() == QLatin1String("application/x-ms-dos-executable") || mime.name() == QLatin1String("application/x-shellscript")) {
888             if (KMessageBox::warningYesNo(nullptr,
889                                           xi18nc("@info", "Do you really want to execute <filename>%1</filename>?", url.toDisplayString(QUrl::PreferLocalFile)),
890                                           QString(),
891                                           KGuiItem(i18n("Execute")),
892                                           KStandardGuiItem::cancel())
893                 != KMessageBox::Yes) {
894                 return true;
895             }
896         }
897         w->checkPhishingUrl();
898         return true;
899     } else {
900         return false;
901     }
902 }
903 
handleDrag(const QUrl & url,ViewerPrivate * window) const904 bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
905 {
906     Q_UNUSED(url)
907     Q_UNUSED(window)
908     return false;
909 }
910 
willHandleDrag(const QUrl & url,ViewerPrivate * window) const911 bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
912 {
913     Q_UNUSED(window)
914     return url.scheme() == QLatin1String("cid");
915 }
916