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