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 SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
471{
472 QString keyId;
473 if (url.scheme() == QStringLiteral("key")) {
474 keyId = url.path();
475 }
476
477 if (keyId.isEmpty()) {
478 return false;
479 }
480
481 QStringList lst;
482 lst << QStringLiteral("--parent-windowid") << QString::number(static_cast<qlonglong>(w->viewer()->mainWindow()->winId())) << QStringLiteral("--query")
483 << keyId;
484#ifdef Q_OS_WIN
485 QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra.exe"), {QCoreApplication::applicationDirPath()});
486 if (exec.isEmpty()) {
487 exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra.exe"));
488 }
489#else
490 const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
491#endif
492 if (exec.isEmpty()) {
493 qCWarning(MESSAGEVIEWER_LOG) << "Could not find kleopatra executable in PATH";
494 KMessageBox::error(w->mMainWindow,
495 i18n("Could not start certificate manager. "
496 "Please check your installation."),
497 i18n("KMail Error"));
498 return false;
499 }
500 QProcess::startDetached(exec, lst);
501 return true;
502}
503
504QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
505{
506 QString keyId;
507 if (url.scheme() == QStringLiteral("key")) {
508 keyId = url.path();
509 }
510
511 if (keyId.isEmpty()) {
512 return {};
513 }
514 return i18n("Show certificate 0x%1", keyId);
515}
516
517bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
518{
519 if (!url.host().isEmpty() || !url.hasFragment()) {
520 return false;
521 }
522
523 w->scrollToAnchor(url.fragment());
524 return true;
525}
526
527QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
528{
529 if (url.scheme() == QLatin1StringView("mailto")) {
531 }
532 return {};
533}
534
535static QString searchFullEmailByUid(const QString &uid)
536{
537 QString fullEmail;
538 auto job = new Akonadi::ContactSearchJob();
539 job->setLimit(1);
541 job->exec();
542 const KContacts::Addressee::List res = job->contacts();
543 if (!res.isEmpty()) {
544 KContacts::Addressee addr = res.at(0);
545 fullEmail = addr.fullEmail();
546 }
547 return fullEmail;
548}
549
550static void runKAddressBook(const QUrl &url)
551{
552 auto job = new Akonadi::OpenEmailAddressJob(url.path(), nullptr);
553 job->start();
554}
555
556bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const
557{
558 if (url.scheme() == QLatin1StringView("uid")) {
559 runKAddressBook(url);
560 return true;
561 } else {
562 return false;
563 }
564}
565
566bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const
567{
568 if (url.scheme() != QLatin1StringView("uid") || url.path().isEmpty()) {
569 return false;
570 }
571
572 QMenu menu;
573 QAction *open = menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book"));
574#ifndef QT_NO_CLIPBOARD
575 QAction *copy = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address"));
576#endif
577
578 QAction *a = menu.exec(p);
579 if (a == open) {
580 runKAddressBook(url);
581#ifndef QT_NO_CLIPBOARD
582 } else if (a == copy) {
583 const QString fullEmail = searchFullEmailByUid(url.path());
584 if (!fullEmail.isEmpty()) {
586 clip->setText(fullEmail, QClipboard::Clipboard);
587 clip->setText(fullEmail, QClipboard::Selection);
588 PimCommon::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
589 }
590#endif
591 }
592
593 return true;
594}
595
596QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
597{
598 if (url.scheme() == QLatin1StringView("uid")) {
599 return i18n("Lookup the contact in KAddressbook");
600 } else {
601 return {};
602 }
603}
604
605KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const
606{
607 if (!w || !w->mMessage) {
608 return nullptr;
609 }
610 if (url.scheme() == QLatin1StringView("attachment")) {
611 KMime::Content *node = w->nodeFromUrl(url);
612 return node;
613 }
614 return nullptr;
615}
616
617bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const
618{
619 bool inHeader = false;
620 QUrlQuery query(url);
621 const QString place = query.queryItemValue(QStringLiteral("place")).toLower();
622 if (!place.isNull()) {
623 inHeader = (place == QLatin1StringView("header"));
624 }
625 return inHeader;
626}
627
628bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
629{
630 KMime::Content *node = nodeForUrl(url, w);
631 if (!node) {
632 return false;
633 }
634 const bool inHeader = attachmentIsInHeader(url);
635 const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
636 if (inHeader) {
637 w->scrollToAttachment(node);
638 }
639 // if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
640 w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
641 //}
642
643 return true;
644}
645
646bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
647{
648 KMime::Content *node = nodeForUrl(url, window);
649 if (!node) {
650 return false;
651 }
652 if (!window) {
653 return false;
654 }
655 if (node->contentType()->mimeType() == "text/x-moz-deleted") {
656 return false;
657 }
658
659 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
660 if (isEncapsulatedMessage) {
662 message->setContent(node->parent()->bodyAsMessage()->encodedContent());
663 message->parse();
664 Akonadi::Item item;
665 item.setPayload<KMime::Message::Ptr>(message);
668 QUrl newUrl;
669 if (MessageViewer::Util::saveMessageInMboxAndGetUrl(newUrl, Akonadi::Item::List() << item, window->viewer())) {
670 window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl);
671 }
672 } else {
673 QList<QUrl> urlList;
674 if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) {
675 window->viewer()->showOpenAttachmentFolderWidget(urlList);
676 }
677 }
678
679 return true;
680}
681
682bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
683{
684 return nodeForUrl(url, window) != nullptr;
685}
686
687bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
688{
689#ifndef QT_NO_DRAGANDDROP
690 KMime::Content *node = nodeForUrl(url, window);
691 if (!node) {
692 return false;
693 }
694 if (node->contentType()->mimeType() == "text/x-moz-deleted") {
695 return false;
696 }
697 QString fileName;
698 QUrl tUrl;
699 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
700 if (isEncapsulatedMessage) {
702 message->setContent(node->parent()->bodyAsMessage()->encodedContent());
703 message->parse();
704 Akonadi::Item item;
705 item.setPayload<KMime::Message::Ptr>(message);
708 fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
709
710 KMBox::MBox mbox;
711 QFile::remove(fileName);
712
713 if (!mbox.load(fileName)) {
714 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file";
715 return false;
716 }
718
719 if (!mbox.save()) {
720 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file";
721 return false;
722 }
723 tUrl = QUrl::fromLocalFile(fileName);
724 } else {
725 if (node->header<KMime::Headers::Subject>()) {
726 if (!node->contents().isEmpty()) {
727 node = node->contents().constLast();
728 fileName = window->nodeHelper()->writeNodeToTempFile(node);
729 tUrl = QUrl::fromLocalFile(fileName);
730 }
731 }
732 if (fileName.isEmpty()) {
733 tUrl = window->nodeHelper()->tempFileUrlFromNode(node);
734 fileName = tUrl.path();
735 }
736 }
737 if (!fileName.isEmpty()) {
738 QFile f(fileName);
740 const QString icon = Util::iconPathForContent(node, KIconLoader::Small);
741 auto drag = new QDrag(window->viewer());
742 auto mimeData = new QMimeData();
743 mimeData->setUrls(QList<QUrl>() << tUrl);
744 drag->setMimeData(mimeData);
745 if (!icon.isEmpty()) {
746 drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
747 }
748 drag->exec();
749 return true;
750 } else {
751#endif
752 return false;
753 }
754}
755
756bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
757{
758 KMime::Content *node = nodeForUrl(url, w);
759 if (!node) {
760 return false;
761 }
762 // PENDING(romain_kdab) : replace with toLocalFile() ?
763 w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
764 return true;
765}
766
767QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
768{
769 KMime::Content *node = nodeForUrl(url, w);
770 if (!node) {
771 return {};
772 }
774 if (!name.isEmpty()) {
775 return i18n("Attachment: %1", name);
776 } else if (dynamic_cast<KMime::Message *>(node)) {
777 if (node->header<KMime::Headers::Subject>()) {
778 return i18n("Encapsulated Message (Subject: %1)", node->header<KMime::Headers::Subject>()->asUnicodeString());
779 } else {
780 return i18n("Encapsulated Message");
781 }
782 }
783 return i18n("Unnamed attachment");
784}
785
786static QString extractAuditLog(const QUrl &url)
787{
788 if (url.scheme() != QLatin1StringView("kmail") || url.path() != QLatin1StringView("showAuditLog")) {
789 return {};
790 }
791 QUrlQuery query(url);
792 Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty());
793 return query.queryItemValue(QStringLiteral("log"));
794}
795
796bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
797{
798 const QString auditLog = extractAuditLog(url);
799 if (auditLog.isEmpty()) {
800 return false;
801 }
802 Kleo::AuditLogViewer::showAuditLog(w->mMainWindow, Kleo::AuditLogEntry{auditLog, GpgME::Error{}}, auditLog);
803 return true;
804}
805
806bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const
807{
808 Q_UNUSED(w)
809 // disable RMB for my own links:
810 return !extractAuditLog(url).isEmpty();
811}
812
813QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
814{
815 if (extractAuditLog(url).isEmpty()) {
816 return {};
817 } else {
818 return i18n("Show GnuPG Audit Log for this operation");
819 }
820}
821
822bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
823{
824 Q_UNUSED(url)
825 Q_UNUSED(window)
826 return true;
827}
828
829bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
830{
831 Q_UNUSED(window)
832 Q_UNUSED(url)
833
834 // This will only be called when willHandleDrag() was true. Return false here, that will
835 // notify ViewerPrivate::eventFilter() that no drag was started.
836 return false;
837}
838
839bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
840{
841 Q_UNUSED(window)
842 if (url.scheme() == QLatin1StringView("data") && url.path().startsWith(QLatin1StringView("image"))) {
843 return true;
844 }
845
846 const QString imagePath =
848 return url.path().contains(imagePath);
849}
850
851bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
852{
853 const QString scheme(url.scheme());
854 if ((scheme == QLatin1StringView("http")) || (scheme == QLatin1StringView("https")) || (scheme == QLatin1StringView("ftp"))
855 || (scheme == QLatin1StringView("file")) || (scheme == QLatin1StringView("ftps")) || (scheme == QLatin1StringView("sftp"))
856 || (scheme == QLatin1StringView("help")) || (scheme == QLatin1StringView("vnc")) || (scheme == QLatin1StringView("smb"))
857 || (scheme == QLatin1StringView("fish")) || (scheme == QLatin1StringView("news")) || (scheme == QLatin1StringView("tel"))
858 || (scheme == QLatin1StringView("geo")) || (scheme == QLatin1StringView("sms"))) {
859 PimCommon::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL..."));
860 QTimer::singleShot(2s, PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::reset);
861
862 QMimeDatabase mimeDb;
863 auto mime = mimeDb.mimeTypeForUrl(url);
864 if (mime.name() == QLatin1StringView("application/x-desktop") || mime.name() == QLatin1StringView("application/x-executable")
865 || mime.name() == QLatin1StringView("application/x-ms-dos-executable") || mime.name() == QLatin1StringView("application/x-shellscript")) {
867 nullptr,
868 xi18nc("@info", "Do you really want to execute <filename>%1</filename>?", url.toDisplayString(QUrl::PreferLocalFile)),
869 QString(),
870 KGuiItem(i18nc("@action:button", "Execute")),
872 != KMessageBox::ButtonCode::PrimaryAction) {
873 return true;
874 }
875 }
876 w->checkPhishingUrl();
877 return true;
878 } else {
879 return false;
880 }
881}
882
883bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
884{
885 Q_UNUSED(url)
886 Q_UNUSED(window)
887 return false;
888}
889
890bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
891{
892 Q_UNUSED(window)
893 return url.scheme() == QLatin1StringView("cid");
894}
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())
const Headers::ContentType * contentType() const
T * header() const
Content * parent()
QSharedPointer< Message > bodyAsMessage() const
bool bodyIsMessage() const
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 i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
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)
QString name(StandardAction id)
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
KGuiItem cancel()
KGuiItem open()
const QList< QKeySequence > & end()
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)
bool contains(QChar ch, 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 Fri Oct 11 2024 12:08:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.