Messagelib

urlhandlermanager.cpp
1/* -*- c++ -*-
2 urlhandlermanager.cpp
3
4 This file is part of KMail, the KDE mail client.
5 SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
6 SPDX-FileCopyrightText: 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
7 SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11
12#include "urlhandlermanager.h"
13#include "../utils/messageviewerutil_p.h"
14#include "interfaces/bodyparturlhandler.h"
15#include "messageviewer/messageviewerutil.h"
16#include "messageviewer_debug.h"
17#include "stl_util.h"
18#include "urlhandlermanager_p.h"
19#include "utils/mimetype.h"
20#include "viewer/viewer_p.h"
21
22#include <MimeTreeParser/NodeHelper>
23#include <MimeTreeParser/PartNodeBodyPart>
24
25#include <Akonadi/OpenEmailAddressJob>
26#include <MessageCore/StringUtil>
27#include <PimCommon/BroadcastStatus>
28
29#include <Akonadi/ContactSearchJob>
30
31#include <Akonadi/MessageFlags>
32#include <KEmailAddress>
33#include <KMbox/MBox>
34#include <KMime/Content>
35
36#include <KIconLoader>
37#include <KLocalizedString>
38#include <KMessageBox>
39
40#include <QApplication>
41#include <QClipboard>
42#include <QDrag>
43#include <QFile>
44#include <QIcon>
45#include <QMenu>
46#include <QMimeData>
47#include <QMimeDatabase>
48#include <QProcess>
49#include <QStandardPaths>
50#include <QUrl>
51#include <QUrlQuery>
52
53#include <algorithm>
54
55#include <Libkleo/AuditLogEntry>
56#include <Libkleo/AuditLogViewer>
57#include <chrono>
58
59using namespace std::chrono_literals;
60
61using std::for_each;
62using std::remove;
63using namespace MessageViewer;
64using namespace MessageCore;
65
66URLHandlerManager *URLHandlerManager::self = nullptr;
67
68//
69//
70// BodyPartURLHandlerManager
71//
72//
73
74BodyPartURLHandlerManager::~BodyPartURLHandlerManager()
75{
76 for_each(mHandlers.begin(), mHandlers.end(), [](QList<const Interface::BodyPartURLHandler *> &handlers) {
77 for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero<Interface::BodyPartURLHandler>());
78 });
79}
80
81void 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
95void 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
109static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path)
110{
111 Q_ASSERT(path);
112
113 if (!w || url.scheme() != QLatin1StringView("x-kmail")) {
114 return nullptr;
115 }
116 const QString urlPath = url.path();
117
118 // urlPath format is: /bodypart/<random number>/<part id>/<path>
119
120 qCDebug(MESSAGEVIEWER_LOG) << "BodyPartURLHandler: urlPath ==" << urlPath;
121 if (!urlPath.startsWith(QLatin1StringView("/bodypart/"))) {
122 return nullptr;
123 }
124
125 const QStringList urlParts = urlPath.mid(10).split(QLatin1Char('/'));
126 if (urlParts.size() != 3) {
127 return nullptr;
128 }
129 // KMime::ContentIndex index( urlParts[1] );
130 QByteArray query(urlParts.at(2).toLatin1());
131 if (url.hasQuery()) {
132 query += "?" + url.query().toLatin1();
133 }
135 return w->nodeFromUrl(QUrl(urlParts.at(1)));
136}
137
138QList<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
154bool BodyPartURLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
155{
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
175bool BodyPartURLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
176{
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
195QString BodyPartURLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
196{
198 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
199 if (!node) {
200 return {};
201 }
202
203 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
204
205 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
206 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
207 const QString msg = (*it)->statusBarMessage(&part, path);
208 if (!msg.isEmpty()) {
209 return msg;
210 }
211 }
212 }
213 return {};
214}
215
216//
217//
218// URLHandlerManager
219//
220//
221
222URLHandlerManager::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
238URLHandlerManager::~URLHandlerManager()
239{
240 for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero<MimeTreeParser::URLHandler>());
241}
242
243URLHandlerManager *URLHandlerManager::instance()
244{
245 if (!self) {
246 self = new URLHandlerManager();
247 }
248 return self;
249}
250
251void 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
260void 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
266void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
267{
268 if (mBodyPartURLHandlerManager) {
269 mBodyPartURLHandlerManager->registerHandler(handler, mimeType);
270 }
271}
272
273void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
274{
275 if (mBodyPartURLHandlerManager) {
276 mBodyPartURLHandlerManager->unregisterHandler(handler);
277 }
278}
279
280bool 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
291bool 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
302bool 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
314bool 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
325bool 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
336QString URLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
337{
338 HandlerList::const_iterator end(mHandlers.constEnd());
339 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
340 const QString msg = (*it)->statusBarMessage(url, w);
341 if (!msg.isEmpty()) {
342 return msg;
343 }
344 }
345 return {};
346}
347
348//
349//
350// URLHandler
351//
352//
353
354bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
355{
356 if (url.scheme() == QLatin1StringView("kmail")) {
357 if (!w) {
358 return false;
359 }
360 const QString urlPath(url.path());
361 if (urlPath == QLatin1StringView("showHTML")) {
362 w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html);
363 w->update(MimeTreeParser::Force);
364 return true;
365 } else if (urlPath == QLatin1StringView("goOnline")) {
366 w->goOnline();
367 return true;
368 } else if (urlPath == QLatin1StringView("goResourceOnline")) {
369 w->goResourceOnline();
370 return true;
371 } else if (urlPath == QLatin1StringView("loadExternal")) {
372 w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride());
373 w->update(MimeTreeParser::Force);
374 return true;
375 } else if (urlPath == QLatin1StringView("decryptMessage")) {
376 w->setDecryptMessageOverwrite(true);
377 w->update(MimeTreeParser::Force);
378 return true;
379 } else if (urlPath == QLatin1StringView("showSignatureDetails")) {
380 w->setShowSignatureDetails(true);
381 w->update(MimeTreeParser::Force);
382 return true;
383 } else if (urlPath == QLatin1StringView("hideSignatureDetails")) {
384 w->setShowSignatureDetails(false);
385 w->update(MimeTreeParser::Force);
386 return true;
387 } else if (urlPath == QLatin1StringView("showEncryptionDetails")) {
388 w->setShowEncryptionDetails(true);
389 w->update(MimeTreeParser::Force);
390 return true;
391 } else if (urlPath == QLatin1StringView("hideEncryptionDetails")) {
392 w->setShowEncryptionDetails(false);
393 w->update(MimeTreeParser::Force);
394 return true;
395 }
396 }
397 return false;
398}
399
400QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
401{
402 const QString schemeStr = url.scheme();
403 if (schemeStr == QLatin1StringView("kmail")) {
404 const QString urlPath(url.path());
405 if (urlPath == QLatin1StringView("showHTML")) {
406 return i18n("Turn on HTML rendering for this message.");
407 } else if (urlPath == QLatin1StringView("loadExternal")) {
408 return i18n("Load external references from the Internet for this message.");
409 } else if (urlPath == QLatin1StringView("goOnline")) {
410 return i18n("Work online.");
411 } else if (urlPath == QLatin1StringView("goResourceOnline")) {
412 return i18n("Make account online.");
413 } else if (urlPath == QLatin1StringView("decryptMessage")) {
414 return i18n("Decrypt message.");
415 } else if (urlPath == QLatin1StringView("showSignatureDetails")) {
416 return i18n("Show signature details.");
417 } else if (urlPath == QLatin1StringView("hideSignatureDetails")) {
418 return i18n("Hide signature details.");
419 } else if (urlPath == QLatin1StringView("showEncryptionDetails")) {
420 return i18n("Show encryption details.");
421 } else if (urlPath == QLatin1StringView("hideEncryptionDetails")) {
422 return i18n("Hide encryption details.");
423 } else {
424 return {};
425 }
426 } else if (schemeStr == QLatin1StringView("help")) {
427 return i18n("Open Documentation");
428 }
429 return {};
430}
431
432bool ExpandCollapseQuoteURLManager::handleClick(const QUrl &url, ViewerPrivate *w) const
433{
434 // kmail:levelquote/?num -> the level quote to collapse.
435 // kmail:levelquote/?-num -> expand all levels quote.
436 if (url.scheme() == QLatin1StringView("kmail") && url.path() == QLatin1StringView("levelquote")) {
437 const QString levelStr = url.query();
438 bool isNumber = false;
439 const int levelQuote = levelStr.toInt(&isNumber);
440 if (isNumber) {
441 w->slotLevelQuote(levelQuote);
442 }
443 return true;
444 }
445 return false;
446}
447
448bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
449{
450 Q_UNUSED(url)
451 Q_UNUSED(window)
452 return false;
453}
454
455QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const
456{
457 if (url.scheme() == QLatin1StringView("kmail") && url.path() == QLatin1StringView("levelquote")) {
458 const QString query = url.query();
459 if (query.length() >= 1) {
460 if (query[0] == QLatin1Char('-')) {
461 return i18n("Expand all quoted text.");
462 } else {
463 return i18n("Collapse quoted text.");
464 }
465 }
466 }
467 return {};
468}
469
470bool foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId)
471{
472 static QString showCertMan(QStringLiteral("showCertificate#"));
474 libName.clear();
475 keyId.clear();
476 int i1 = aUrl.indexOf(showCertMan);
477 if (-1 < i1) {
478 i1 += showCertMan.length();
479 int i2 = aUrl.indexOf(QLatin1StringView(" ### "), i1);
480 if (i1 < i2) {
481 displayName = aUrl.mid(i1, i2 - i1);
482 i1 = i2 + 5;
483 i2 = aUrl.indexOf(QLatin1StringView(" ### "), i1);
484 if (i1 < i2) {
485 libName = aUrl.mid(i1, i2 - i1);
486 i2 += 5;
487
488 keyId = aUrl.mid(i2);
489 /*
490 int len = aUrl.length();
491 if( len > i2+1 ) {
492 keyId = aUrl.mid( i2, 2 );
493 i2 += 2;
494 while( len > i2+1 ) {
495 keyId += ':';
496 keyId += aUrl.mid( i2, 2 );
497 i2 += 2;
498 }
499 }
500 */
501 }
502 }
503 }
504 return !keyId.isEmpty();
505}
506
507bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
508{
509 if (!url.hasFragment()) {
510 return false;
511 }
513 QString libName;
514 QString keyId;
515 if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) {
516 return false;
517 }
518 QStringList lst;
519 lst << QStringLiteral("--parent-windowid") << QString::number(static_cast<qlonglong>(w->viewer()->mainWindow()->winId())) << QStringLiteral("--query")
520 << keyId;
521#ifdef Q_OS_WIN
522 QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra.exe"), {QCoreApplication::applicationDirPath()});
523 if (exec.isEmpty()) {
524 exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra.exe"));
525 }
526#else
527 const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
528#endif
529 if (exec.isEmpty()) {
530 qCWarning(MESSAGEVIEWER_LOG) << "Could not find kleopatra executable in PATH";
531 KMessageBox::error(w->mMainWindow,
532 i18n("Could not start certificate manager. "
533 "Please check your installation."),
534 i18n("KMail Error"));
535 return false;
536 }
537 QProcess::startDetached(exec, lst);
538 return true;
539}
540
541QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
542{
544 QString libName;
545 QString keyId;
546 if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) {
547 return {};
548 }
549 return i18n("Show certificate 0x%1", keyId);
550}
551
552bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
553{
554 if (!url.host().isEmpty() || !url.hasFragment()) {
555 return false;
556 }
557
558 w->scrollToAnchor(url.fragment());
559 return true;
560}
561
562QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
563{
564 if (url.scheme() == QLatin1StringView("mailto")) {
566 }
567 return {};
568}
569
570static QString searchFullEmailByUid(const QString &uid)
571{
572 QString fullEmail;
573 auto job = new Akonadi::ContactSearchJob();
574 job->setLimit(1);
576 job->exec();
577 const KContacts::Addressee::List res = job->contacts();
578 if (!res.isEmpty()) {
579 KContacts::Addressee addr = res.at(0);
580 fullEmail = addr.fullEmail();
581 }
582 return fullEmail;
583}
584
585static void runKAddressBook(const QUrl &url)
586{
587 auto job = new Akonadi::OpenEmailAddressJob(url.path(), nullptr);
588 job->start();
589}
590
591bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const
592{
593 if (url.scheme() == QLatin1StringView("uid")) {
594 runKAddressBook(url);
595 return true;
596 } else {
597 return false;
598 }
599}
600
601bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const
602{
603 if (url.scheme() != QLatin1StringView("uid") || url.path().isEmpty()) {
604 return false;
605 }
606
607 QMenu menu;
608 QAction *open = menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book"));
609#ifndef QT_NO_CLIPBOARD
610 QAction *copy = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address"));
611#endif
612
613 QAction *a = menu.exec(p);
614 if (a == open) {
615 runKAddressBook(url);
616#ifndef QT_NO_CLIPBOARD
617 } else if (a == copy) {
618 const QString fullEmail = searchFullEmailByUid(url.path());
619 if (!fullEmail.isEmpty()) {
621 clip->setText(fullEmail, QClipboard::Clipboard);
622 clip->setText(fullEmail, QClipboard::Selection);
623 PimCommon::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
624 }
625#endif
626 }
627
628 return true;
629}
630
631QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
632{
633 if (url.scheme() == QLatin1StringView("uid")) {
634 return i18n("Lookup the contact in KAddressbook");
635 } else {
636 return {};
637 }
638}
639
640KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const
641{
642 if (!w || !w->mMessage) {
643 return nullptr;
644 }
645 if (url.scheme() == QLatin1StringView("attachment")) {
646 KMime::Content *node = w->nodeFromUrl(url);
647 return node;
648 }
649 return nullptr;
650}
651
652bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const
653{
654 bool inHeader = false;
655 QUrlQuery query(url);
656 const QString place = query.queryItemValue(QStringLiteral("place")).toLower();
657 if (!place.isNull()) {
658 inHeader = (place == QLatin1StringView("header"));
659 }
660 return inHeader;
661}
662
663bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
664{
665 KMime::Content *node = nodeForUrl(url, w);
666 if (!node) {
667 return false;
668 }
669 const bool inHeader = attachmentIsInHeader(url);
670 const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
671 if (inHeader) {
672 w->scrollToAttachment(node);
673 }
674 // if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
675 w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
676 //}
677
678 return true;
679}
680
681bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
682{
683 KMime::Content *node = nodeForUrl(url, window);
684 if (!node) {
685 return false;
686 }
687 if (!window) {
688 return false;
689 }
690 if (node->contentType()->mimeType() == "text/x-moz-deleted") {
691 return false;
692 }
693
694 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
695 if (isEncapsulatedMessage) {
697 message->setContent(node->parent()->bodyAsMessage()->encodedContent());
698 message->parse();
699 Akonadi::Item item;
700 item.setPayload<KMime::Message::Ptr>(message);
703 QUrl newUrl;
704 if (MessageViewer::Util::saveMessageInMboxAndGetUrl(newUrl, Akonadi::Item::List() << item, window->viewer())) {
705 window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl);
706 }
707 } else {
708 QList<QUrl> urlList;
709 if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) {
710 window->viewer()->showOpenAttachmentFolderWidget(urlList);
711 }
712 }
713
714 return true;
715}
716
717bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
718{
719 return nodeForUrl(url, window) != nullptr;
720}
721
722bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
723{
724#ifndef QT_NO_DRAGANDDROP
725 KMime::Content *node = nodeForUrl(url, window);
726 if (!node) {
727 return false;
728 }
729 if (node->contentType()->mimeType() == "text/x-moz-deleted") {
730 return false;
731 }
732 QString fileName;
733 QUrl tUrl;
734 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
735 if (isEncapsulatedMessage) {
737 message->setContent(node->parent()->bodyAsMessage()->encodedContent());
738 message->parse();
739 Akonadi::Item item;
740 item.setPayload<KMime::Message::Ptr>(message);
743 fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
744
745 KMBox::MBox mbox;
746 QFile::remove(fileName);
747
748 if (!mbox.load(fileName)) {
749 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file";
750 return false;
751 }
753
754 if (!mbox.save()) {
755 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file";
756 return false;
757 }
758 tUrl = QUrl::fromLocalFile(fileName);
759 } else {
760 if (node->header<KMime::Headers::Subject>()) {
761 if (!node->contents().isEmpty()) {
762 node = node->contents().constLast();
763 fileName = window->nodeHelper()->writeNodeToTempFile(node);
764 tUrl = QUrl::fromLocalFile(fileName);
765 }
766 }
767 if (fileName.isEmpty()) {
768 tUrl = window->nodeHelper()->tempFileUrlFromNode(node);
769 fileName = tUrl.path();
770 }
771 }
772 if (!fileName.isEmpty()) {
773 QFile f(fileName);
775 const QString icon = Util::iconPathForContent(node, KIconLoader::Small);
776 auto drag = new QDrag(window->viewer());
777 auto mimeData = new QMimeData();
778 mimeData->setUrls(QList<QUrl>() << tUrl);
779 drag->setMimeData(mimeData);
780 if (!icon.isEmpty()) {
781 drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
782 }
783 drag->exec();
784 return true;
785 } else {
786#endif
787 return false;
788 }
789}
790
791bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
792{
793 KMime::Content *node = nodeForUrl(url, w);
794 if (!node) {
795 return false;
796 }
797 // PENDING(romain_kdab) : replace with toLocalFile() ?
798 w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
799 return true;
800}
801
802QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
803{
804 KMime::Content *node = nodeForUrl(url, w);
805 if (!node) {
806 return {};
807 }
809 if (!name.isEmpty()) {
810 return i18n("Attachment: %1", name);
811 } else if (dynamic_cast<KMime::Message *>(node)) {
812 if (node->header<KMime::Headers::Subject>()) {
813 return i18n("Encapsulated Message (Subject: %1)", node->header<KMime::Headers::Subject>()->asUnicodeString());
814 } else {
815 return i18n("Encapsulated Message");
816 }
817 }
818 return i18n("Unnamed attachment");
819}
820
821static QString extractAuditLog(const QUrl &url)
822{
823 if (url.scheme() != QLatin1StringView("kmail") || url.path() != QLatin1StringView("showAuditLog")) {
824 return {};
825 }
826 QUrlQuery query(url);
827 Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty());
828 return query.queryItemValue(QStringLiteral("log"));
829}
830
831bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
832{
833 const QString auditLog = extractAuditLog(url);
834 if (auditLog.isEmpty()) {
835 return false;
836 }
837 Kleo::AuditLogViewer::showAuditLog(w->mMainWindow, Kleo::AuditLogEntry{auditLog, GpgME::Error{}}, auditLog);
838 return true;
839}
840
841bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const
842{
843 Q_UNUSED(w)
844 // disable RMB for my own links:
845 return !extractAuditLog(url).isEmpty();
846}
847
848QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
849{
850 if (extractAuditLog(url).isEmpty()) {
851 return {};
852 } else {
853 return i18n("Show GnuPG Audit Log for this operation");
854 }
855}
856
857bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
858{
859 Q_UNUSED(url)
860 Q_UNUSED(window)
861 return true;
862}
863
864bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
865{
866 Q_UNUSED(window)
867 Q_UNUSED(url)
868
869 // This will only be called when willHandleDrag() was true. Return false here, that will
870 // notify ViewerPrivate::eventFilter() that no drag was started.
871 return false;
872}
873
874bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
875{
876 Q_UNUSED(window)
877 if (url.scheme() == QLatin1StringView("data") && url.path().startsWith(QLatin1StringView("image"))) {
878 return true;
879 }
880
881 const QString imagePath =
883 return url.path().contains(imagePath);
884}
885
886bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
887{
888 const QString scheme(url.scheme());
889 if ((scheme == QLatin1StringView("http")) || (scheme == QLatin1StringView("https")) || (scheme == QLatin1StringView("ftp"))
890 || (scheme == QLatin1StringView("file")) || (scheme == QLatin1StringView("ftps")) || (scheme == QLatin1StringView("sftp"))
891 || (scheme == QLatin1StringView("help")) || (scheme == QLatin1StringView("vnc")) || (scheme == QLatin1StringView("smb"))
892 || (scheme == QLatin1StringView("fish")) || (scheme == QLatin1StringView("news")) || (scheme == QLatin1StringView("tel"))
893 || (scheme == QLatin1StringView("geo")) || (scheme == QLatin1StringView("sms"))) {
894 PimCommon::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL..."));
895 QTimer::singleShot(2s, PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::reset);
896
897 QMimeDatabase mimeDb;
898 auto mime = mimeDb.mimeTypeForUrl(url);
899 if (mime.name() == QLatin1StringView("application/x-desktop") || mime.name() == QLatin1StringView("application/x-executable")
900 || mime.name() == QLatin1StringView("application/x-ms-dos-executable") || mime.name() == QLatin1StringView("application/x-shellscript")) {
902 nullptr,
903 xi18nc("@info", "Do you really want to execute <filename>%1</filename>?", url.toDisplayString(QUrl::PreferLocalFile)),
904 QString(),
905 KGuiItem(i18n("Execute")),
907 != KMessageBox::ButtonCode::PrimaryAction) {
908 return true;
909 }
910 }
911 w->checkPhishingUrl();
912 return true;
913 } else {
914 return false;
915 }
916}
917
918bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
919{
920 Q_UNUSED(url)
921 Q_UNUSED(window)
922 return false;
923}
924
925bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
926{
927 Q_UNUSED(window)
928 return url.scheme() == QLatin1StringView("cid");
929}
T payload() const
void setMimeType(const QString &mimeType)
void setPayload(const T &p)
AddresseeList List
QString fullEmail(const QString &email=QString()) const
bool load(const QString &fileName)
MBoxEntry appendMessage(const KMime::Message::Ptr &message)
bool save(const QString &fileName=QString())
Headers::ContentType * contentType(bool create=true)
Content * parent() const
QSharedPointer< Message > bodyAsMessage() const
bool bodyIsMessage() const
T * header(bool create=false)
QList< Content * > contents() const
QByteArray mimeType() const
QString asUnicodeString() const override
static QString mimeType()
An interface to body part reader link handlers.
Singleton to manage the list of URLHandlers.
static QString fileName(const KMime::Content *node)
Returns a usable filename for a node, that can be the filename from the content disposition header,...
an implementation of the BodyPart interface using KMime::Content's
An interface to reader link handlers.
Definition urlhandler.h:30
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
AKONADI_MIME_EXPORT void copyMessageFlags(KMime::Message &from, Akonadi::Item &to)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KCALUTILS_EXPORT QString mimeType()
KCODECS_EXPORT QString decodeMailtoUrl(const QUrl &mailtoUrl)
QWidget * window(QObject *job)
QString path(const QString &relativePath)
ButtonCode warningTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KGuiItem cancel()
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
const QList< QKeySequence > & copy()
const QList< QKeySequence > & open()
QByteArray & insert(qsizetype i, QByteArrayView data)
void push_back(QByteArrayView str)
void setText(const QString &text, Mode mode)
QString applicationDirPath()
bool remove()
QClipboard * clipboard()
QIcon fromTheme(const QString &name)
const_reference at(qsizetype i) const const
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
qsizetype length() const const
void push_back(parameter_type value)
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * exec()
QMimeType mimeTypeForUrl(const QUrl &url) const const
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
PreferLocalFile
QString fragment(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
QString fromPercentEncoding(const QByteArray &input)
bool hasFragment() const const
bool hasQuery() const const
QString host(ComponentFormattingOptions options) const const
QString path(ComponentFormattingOptions options) const const
QString query(ComponentFormattingOptions options) const const
QString scheme() const const
QString toDisplayString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.