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#include <PimCommon/PimUtil>
29
30#include <Akonadi/ContactSearchJob>
31
32#include <Akonadi/MessageFlags>
33#include <KEmailAddress>
34#include <KMbox/MBox>
35#include <KMime/Content>
36
37#include <KIconLoader>
38#include <KLocalizedString>
39#include <KMessageBox>
40
41#include <QApplication>
42#include <QClipboard>
43#include <QDrag>
44#include <QFile>
45#include <QIcon>
46#include <QMenu>
47#include <QMimeData>
48#include <QMimeDatabase>
49#include <QProcess>
50#include <QStandardPaths>
51#include <QUrl>
52#include <QUrlQuery>
53
54#include <algorithm>
55
56#include <Libkleo/AuditLogEntry>
57#include <Libkleo/AuditLogViewer>
58#include <chrono>
59
60using namespace std::chrono_literals;
61
62using std::for_each;
63using std::remove;
64using namespace MessageViewer;
65using namespace MessageCore;
66
67URLHandlerManager *URLHandlerManager::self = nullptr;
68
69//
70//
71// BodyPartURLHandlerManager
72//
73//
74
75BodyPartURLHandlerManager::~BodyPartURLHandlerManager()
76{
77 for_each(mHandlers.begin(), mHandlers.end(), [](QList<const Interface::BodyPartURLHandler *> &handlers) {
78 for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero<Interface::BodyPartURLHandler>());
79 });
80}
81
82void BodyPartURLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
83{
84 if (!handler) {
85 return;
86 }
87 unregisterHandler(handler); // don't produce duplicates
88 const auto mt = mimeType.toLatin1();
89 auto it = mHandlers.find(mt);
90 if (it == mHandlers.end()) {
91 it = mHandlers.insert(mt, {});
92 }
93 it->push_back(handler);
94}
95
96void BodyPartURLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
97{
98 // don't delete them, only remove them from the list!
99 auto it = mHandlers.begin();
100 while (it != mHandlers.end()) {
101 it->erase(remove(it->begin(), it->end(), handler), it->end());
102 if (it->isEmpty()) {
103 it = mHandlers.erase(it);
104 } else {
105 ++it;
106 }
107 }
108}
109
110static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path)
111{
112 Q_ASSERT(path);
113
114 if (!w || url.scheme() != QLatin1StringView("x-kmail")) {
115 return nullptr;
116 }
117 const QString urlPath = url.path();
118
119 // urlPath format is: /bodypart/<random number>/<part id>/<path>
120
121 qCDebug(MESSAGEVIEWER_LOG) << "BodyPartURLHandler: urlPath ==" << urlPath;
122 if (!urlPath.startsWith(QLatin1StringView("/bodypart/"))) {
123 return nullptr;
124 }
125
126 const QStringList urlParts = urlPath.mid(10).split(QLatin1Char('/'));
127 if (urlParts.size() != 3) {
128 return nullptr;
129 }
130 // KMime::ContentIndex index( urlParts[1] );
131 QByteArray query(urlParts.at(2).toLatin1());
132 if (url.hasQuery()) {
133 query += "?" + url.query().toLatin1();
134 }
136 return w->nodeFromUrl(QUrl(urlParts.at(1)));
137}
138
139QList<const Interface::BodyPartURLHandler *> BodyPartURLHandlerManager::handlersForPart(KMime::Content *node) const
140{
141 if (auto ct = node->contentType(false)) {
142 auto mimeType = ct->mimeType();
143 if (!mimeType.isEmpty()) {
144 // Bug 390900
145 if (mimeType == "text/x-vcard") {
146 mimeType = "text/vcard";
147 }
148 return mHandlers.value(mimeType);
149 }
150 }
151
152 return {};
153}
154
155bool BodyPartURLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
156{
158 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
159 if (!node) {
160 return false;
161 }
162
163 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
164
165 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
166 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
167 if ((*it)->handleClick(w->viewer(), &part, path)) {
168 return true;
169 }
170 }
171 }
172
173 return false;
174}
175
176bool BodyPartURLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
177{
179 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
180 if (!node) {
181 return false;
182 }
183
184 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
185
186 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
187 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
188 if ((*it)->handleContextMenuRequest(&part, path, p)) {
189 return true;
190 }
191 }
192 }
193 return false;
194}
195
196QString BodyPartURLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
197{
199 KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
200 if (!node) {
201 return {};
202 }
203
204 MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper());
205
206 for (const auto &handlers : {handlersForPart(node), mHandlers.value({})}) {
207 for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
208 const QString msg = (*it)->statusBarMessage(&part, path);
209 if (!msg.isEmpty()) {
210 return msg;
211 }
212 }
213 }
214 return {};
215}
216
217//
218//
219// URLHandlerManager
220//
221//
222
223URLHandlerManager::URLHandlerManager()
224{
225 registerHandler(new KMailProtocolURLHandler());
226 registerHandler(new ExpandCollapseQuoteURLManager());
227 registerHandler(new SMimeURLHandler());
228 registerHandler(new MailToURLHandler());
229 registerHandler(new ContactUidURLHandler());
230 registerHandler(new HtmlAnchorHandler());
231 registerHandler(new AttachmentURLHandler());
232 registerHandler(mBodyPartURLHandlerManager = new BodyPartURLHandlerManager());
233 registerHandler(new ShowAuditLogURLHandler());
234 registerHandler(new InternalImageURLHandler);
235 registerHandler(new KRunURLHandler());
236 registerHandler(new EmbeddedImageURLHandler());
237}
238
239URLHandlerManager::~URLHandlerManager()
240{
241 for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero<MimeTreeParser::URLHandler>());
242}
243
244URLHandlerManager *URLHandlerManager::instance()
245{
246 if (!self) {
247 self = new URLHandlerManager();
248 }
249 return self;
250}
251
252void URLHandlerManager::registerHandler(const MimeTreeParser::URLHandler *handler)
253{
254 if (!handler) {
255 return;
256 }
257 unregisterHandler(handler); // don't produce duplicates
258 mHandlers.push_back(handler);
259}
260
261void URLHandlerManager::unregisterHandler(const MimeTreeParser::URLHandler *handler)
262{
263 // don't delete them, only remove them from the list!
264 mHandlers.erase(remove(mHandlers.begin(), mHandlers.end(), handler), mHandlers.end());
265}
266
267void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
268{
269 if (mBodyPartURLHandlerManager) {
270 mBodyPartURLHandlerManager->registerHandler(handler, mimeType);
271 }
272}
273
274void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
275{
276 if (mBodyPartURLHandlerManager) {
277 mBodyPartURLHandlerManager->unregisterHandler(handler);
278 }
279}
280
281bool URLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
282{
283 HandlerList::const_iterator end(mHandlers.constEnd());
284 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
285 if ((*it)->handleClick(url, w)) {
286 return true;
287 }
288 }
289 return false;
290}
291
292bool URLHandlerManager::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
293{
294 HandlerList::const_iterator end(mHandlers.constEnd());
295 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
296 if ((*it)->handleShiftClick(url, window)) {
297 return true;
298 }
299 }
300 return false;
301}
302
303bool URLHandlerManager::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
304{
305 HandlerList::const_iterator end(mHandlers.constEnd());
306
307 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
308 if ((*it)->willHandleDrag(url, window)) {
309 return true;
310 }
311 }
312 return false;
313}
314
315bool URLHandlerManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
316{
317 HandlerList::const_iterator end(mHandlers.constEnd());
318 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
319 if ((*it)->handleDrag(url, window)) {
320 return true;
321 }
322 }
323 return false;
324}
325
326bool URLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
327{
328 HandlerList::const_iterator end(mHandlers.constEnd());
329 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
330 if ((*it)->handleContextMenuRequest(url, p, w)) {
331 return true;
332 }
333 }
334 return false;
335}
336
337QString URLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
338{
339 HandlerList::const_iterator end(mHandlers.constEnd());
340 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
341 const QString msg = (*it)->statusBarMessage(url, w);
342 if (!msg.isEmpty()) {
343 return msg;
344 }
345 }
346 return {};
347}
348
349//
350//
351// URLHandler
352//
353//
354
355bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
356{
357 if (url.scheme() == QLatin1StringView("kmail")) {
358 if (!w) {
359 return false;
360 }
361 const QString urlPath(url.path());
362 if (urlPath == QLatin1StringView("showHTML")) {
363 w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html);
364 w->update(MimeTreeParser::Force);
365 return true;
366 } else if (urlPath == QLatin1StringView("goOnline")) {
367 w->goOnline();
368 return true;
369 } else if (urlPath == QLatin1StringView("goResourceOnline")) {
370 w->goResourceOnline();
371 return true;
372 } else if (urlPath == QLatin1StringView("loadExternal")) {
373 w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride());
374 w->update(MimeTreeParser::Force);
375 return true;
376 } else if (urlPath == QLatin1StringView("decryptMessage")) {
377 w->setDecryptMessageOverwrite(true);
378 w->update(MimeTreeParser::Force);
379 return true;
380 } else if (urlPath == QLatin1StringView("showSignatureDetails")) {
381 w->setShowSignatureDetails(true);
382 w->update(MimeTreeParser::Force);
383 return true;
384 } else if (urlPath == QLatin1StringView("hideSignatureDetails")) {
385 w->setShowSignatureDetails(false);
386 w->update(MimeTreeParser::Force);
387 return true;
388 } else if (urlPath == QLatin1StringView("showEncryptionDetails")) {
389 w->setShowEncryptionDetails(true);
390 w->update(MimeTreeParser::Force);
391 return true;
392 } else if (urlPath == QLatin1StringView("hideEncryptionDetails")) {
393 w->setShowEncryptionDetails(false);
394 w->update(MimeTreeParser::Force);
395 return true;
396 }
397 }
398 return false;
399}
400
401QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
402{
403 const QString schemeStr = url.scheme();
404 if (schemeStr == QLatin1StringView("kmail")) {
405 const QString urlPath(url.path());
406 if (urlPath == QLatin1StringView("showHTML")) {
407 return i18n("Turn on HTML rendering for this message.");
408 } else if (urlPath == QLatin1StringView("loadExternal")) {
409 return i18n("Load external references from the Internet for this message.");
410 } else if (urlPath == QLatin1StringView("goOnline")) {
411 return i18n("Work online.");
412 } else if (urlPath == QLatin1StringView("goResourceOnline")) {
413 return i18n("Make account online.");
414 } else if (urlPath == QLatin1StringView("decryptMessage")) {
415 return i18n("Decrypt message.");
416 } else if (urlPath == QLatin1StringView("showSignatureDetails")) {
417 return i18n("Show signature details.");
418 } else if (urlPath == QLatin1StringView("hideSignatureDetails")) {
419 return i18n("Hide signature details.");
420 } else if (urlPath == QLatin1StringView("showEncryptionDetails")) {
421 return i18n("Show encryption details.");
422 } else if (urlPath == QLatin1StringView("hideEncryptionDetails")) {
423 return i18n("Hide encryption details.");
424 } else {
425 return {};
426 }
427 } else if (schemeStr == QLatin1StringView("help")) {
428 return i18n("Open Documentation");
429 }
430 return {};
431}
432
433bool ExpandCollapseQuoteURLManager::handleClick(const QUrl &url, ViewerPrivate *w) const
434{
435 // kmail:levelquote/?num -> the level quote to collapse.
436 // kmail:levelquote/?-num -> expand all levels quote.
437 if (url.scheme() == QLatin1StringView("kmail") && url.path() == QLatin1StringView("levelquote")) {
438 const QString levelStr = url.query();
439 bool isNumber = false;
440 const int levelQuote = levelStr.toInt(&isNumber);
441 if (isNumber) {
442 w->slotLevelQuote(levelQuote);
443 }
444 return true;
445 }
446 return false;
447}
448
449bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
450{
451 Q_UNUSED(url)
452 Q_UNUSED(window)
453 return false;
454}
455
456QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const
457{
458 if (url.scheme() == QLatin1StringView("kmail") && url.path() == QLatin1StringView("levelquote")) {
459 const QString query = url.query();
460 if (query.length() >= 1) {
461 if (query[0] == QLatin1Char('-')) {
462 return i18n("Expand all quoted text.");
463 } else {
464 return i18n("Collapse quoted text.");
465 }
466 }
467 }
468 return {};
469}
470
471bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
472{
473 QString keyId;
474 if (url.scheme() == QLatin1StringView("key")) {
475 keyId = url.path();
476 }
477
478 if (keyId.isEmpty()) {
479 return false;
480 }
481
482 QStringList lst;
483 lst << QStringLiteral("--parent-windowid") << QString::number(static_cast<qlonglong>(w->viewer()->mainWindow()->winId())) << QStringLiteral("--query")
484 << keyId;
485
486 const QString exec = PimCommon::Util::findExecutable(QStringLiteral("kleopatra"));
487
488 if (exec.isEmpty()) {
489 qCWarning(MESSAGEVIEWER_LOG) << "Could not find kleopatra executable in PATH";
490 KMessageBox::error(w->mMainWindow,
491 i18n("Could not start certificate manager. "
492 "Please check your installation."),
493 i18n("KMail Error"));
494 return false;
495 }
496 QProcess::startDetached(exec, lst);
497 return true;
498}
499
500QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
501{
502 QString keyId;
503 if (url.scheme() == QLatin1StringView("key")) {
504 keyId = url.path();
505 }
506
507 if (keyId.isEmpty()) {
508 return {};
509 }
510 return i18n("Show certificate 0x%1", keyId);
511}
512
513bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
514{
515 if (!url.host().isEmpty() || !url.hasFragment()) {
516 return false;
517 }
518
519 w->scrollToAnchor(url.fragment());
520 return true;
521}
522
523QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
524{
525 if (url.scheme() == QLatin1StringView("mailto")) {
527 }
528 return {};
529}
530
531static QString searchFullEmailByUid(const QString &uid)
532{
533 QString fullEmail;
534 auto job = new Akonadi::ContactSearchJob();
535 job->setLimit(1);
537 job->exec();
538 const KContacts::Addressee::List res = job->contacts();
539 if (!res.isEmpty()) {
540 KContacts::Addressee addr = res.at(0);
541 fullEmail = addr.fullEmail();
542 }
543 return fullEmail;
544}
545
546static void runKAddressBook(const QUrl &url)
547{
548 auto job = new Akonadi::OpenEmailAddressJob(url.path(), nullptr);
549 job->start();
550}
551
552bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const
553{
554 if (url.scheme() == QLatin1StringView("uid")) {
555 runKAddressBook(url);
556 return true;
557 } else {
558 return false;
559 }
560}
561
562bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const
563{
564 if (url.scheme() != QLatin1StringView("uid") || url.path().isEmpty()) {
565 return false;
566 }
567
568 QMenu menu;
569 QAction *open = menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book"));
570#ifndef QT_NO_CLIPBOARD
571 QAction *copy = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address"));
572#endif
573
574 QAction *a = menu.exec(p);
575 if (a == open) {
576 runKAddressBook(url);
577#ifndef QT_NO_CLIPBOARD
578 } else if (a == copy) {
579 const QString fullEmail = searchFullEmailByUid(url.path());
580 if (!fullEmail.isEmpty()) {
582 clip->setText(fullEmail, QClipboard::Clipboard);
583 clip->setText(fullEmail, QClipboard::Selection);
584 PimCommon::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
585 }
586#endif
587 }
588
589 return true;
590}
591
592QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
593{
594 if (url.scheme() == QLatin1StringView("uid")) {
595 return i18n("Lookup the contact in KAddressbook");
596 } else {
597 return {};
598 }
599}
600
601KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const
602{
603 if (!w || !w->mMessage) {
604 return nullptr;
605 }
606 if (url.scheme() == QLatin1StringView("attachment")) {
607 KMime::Content *node = w->nodeFromUrl(url);
608 return node;
609 }
610 return nullptr;
611}
612
613bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const
614{
615 bool inHeader = false;
616 QUrlQuery query(url);
617 const QString place = query.queryItemValue(QStringLiteral("place")).toLower();
618 if (!place.isNull()) {
619 inHeader = (place == QLatin1StringView("header"));
620 }
621 return inHeader;
622}
623
624bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
625{
626 KMime::Content *node = nodeForUrl(url, w);
627 if (!node) {
628 return false;
629 }
630 const bool inHeader = attachmentIsInHeader(url);
631 const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
632 if (inHeader) {
633 w->scrollToAttachment(node);
634 }
635 // if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
636 w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
637 //}
638
639 return true;
640}
641
642bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
643{
644 KMime::Content *node = nodeForUrl(url, window);
645 if (!node) {
646 return false;
647 }
648 if (!window) {
649 return false;
650 }
651 if (node->contentType()->mimeType() == "text/x-moz-deleted") {
652 return false;
653 }
654
655 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
656 if (isEncapsulatedMessage) {
658 message->setContent(node->parent()->bodyAsMessage()->encodedContent());
659 message->parse();
660 Akonadi::Item item;
661 item.setPayload<KMime::Message::Ptr>(message);
664 QUrl newUrl;
665 if (MessageViewer::Util::saveMessageInMboxAndGetUrl(newUrl, Akonadi::Item::List() << item, window->viewer())) {
666 window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl);
667 }
668 } else {
669 QList<QUrl> urlList;
670 if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) {
671 window->viewer()->showOpenAttachmentFolderWidget(urlList);
672 }
673 }
674
675 return true;
676}
677
678bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
679{
680 return nodeForUrl(url, window) != nullptr;
681}
682
683bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
684{
685#ifndef QT_NO_DRAGANDDROP
686 KMime::Content *node = nodeForUrl(url, window);
687 if (!node) {
688 return false;
689 }
690 if (node->contentType()->mimeType() == "text/x-moz-deleted") {
691 return false;
692 }
693 QString fileName;
694 QUrl tUrl;
695 const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
696 if (isEncapsulatedMessage) {
698 message->setContent(node->parent()->bodyAsMessage()->encodedContent());
699 message->parse();
700 Akonadi::Item item;
701 item.setPayload<KMime::Message::Ptr>(message);
704 fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
705
706 KMBox::MBox mbox;
707 QFile::remove(fileName);
708
709 if (!mbox.load(fileName)) {
710 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file";
711 return false;
712 }
714
715 if (!mbox.save()) {
716 qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file";
717 return false;
718 }
719 tUrl = QUrl::fromLocalFile(fileName);
720 } else {
721 if (node->header<KMime::Headers::Subject>()) {
722 if (!node->contents().isEmpty()) {
723 node = node->contents().constLast();
724 fileName = window->nodeHelper()->writeNodeToTempFile(node);
725 tUrl = QUrl::fromLocalFile(fileName);
726 }
727 }
728 if (fileName.isEmpty()) {
729 tUrl = window->nodeHelper()->tempFileUrlFromNode(node);
730 fileName = tUrl.path();
731 }
732 }
733 if (!fileName.isEmpty()) {
734 QFile f(fileName);
736 const QString icon = Util::iconPathForContent(node, KIconLoader::Small);
737 auto drag = new QDrag(window->viewer());
738 auto mimeData = new QMimeData();
739 mimeData->setUrls(QList<QUrl>() << tUrl);
740 drag->setMimeData(mimeData);
741 if (!icon.isEmpty()) {
742 drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
743 }
744 drag->exec();
745 return true;
746 } else {
747#endif
748 return false;
749 }
750}
751
752bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
753{
754 KMime::Content *node = nodeForUrl(url, w);
755 if (!node) {
756 return false;
757 }
758 // PENDING(romain_kdab) : replace with toLocalFile() ?
759 w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
760 return true;
761}
762
763QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
764{
765 KMime::Content *node = nodeForUrl(url, w);
766 if (!node) {
767 return {};
768 }
770 if (!name.isEmpty()) {
771 return i18n("Attachment: %1", name);
772 } else if (dynamic_cast<KMime::Message *>(node)) {
773 if (node->header<KMime::Headers::Subject>()) {
774 return i18n("Encapsulated Message (Subject: %1)", node->header<KMime::Headers::Subject>()->asUnicodeString());
775 } else {
776 return i18n("Encapsulated Message");
777 }
778 }
779 return i18n("Unnamed attachment");
780}
781
782static QString extractAuditLog(const QUrl &url)
783{
784 if (url.scheme() != QLatin1StringView("kmail") || url.path() != QLatin1StringView("showAuditLog")) {
785 return {};
786 }
787 QUrlQuery query(url);
788 Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty());
789 return query.queryItemValue(QStringLiteral("log"));
790}
791
792bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
793{
794 const QString auditLog = extractAuditLog(url);
795 if (auditLog.isEmpty()) {
796 return false;
797 }
798 Kleo::AuditLogViewer::showAuditLog(w->mMainWindow, Kleo::AuditLogEntry{auditLog, GpgME::Error{}}, auditLog);
799 return true;
800}
801
802bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const
803{
804 Q_UNUSED(w)
805 // disable RMB for my own links:
806 return !extractAuditLog(url).isEmpty();
807}
808
809QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
810{
811 if (extractAuditLog(url).isEmpty()) {
812 return {};
813 } else {
814 return i18n("Show GnuPG Audit Log for this operation");
815 }
816}
817
818bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
819{
820 Q_UNUSED(url)
821 Q_UNUSED(window)
822 return true;
823}
824
825bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
826{
827 Q_UNUSED(window)
828 Q_UNUSED(url)
829
830 // This will only be called when willHandleDrag() was true. Return false here, that will
831 // notify ViewerPrivate::eventFilter() that no drag was started.
832 return false;
833}
834
835bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
836{
837 Q_UNUSED(window)
838 if (url.scheme() == QLatin1StringView("data") && url.path().startsWith(QLatin1StringView("image"))) {
839 return true;
840 }
841
842 const QString imagePath =
844 return url.path().contains(imagePath);
845}
846
847bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
848{
849 const QString scheme(url.scheme());
850 if ((scheme == QLatin1StringView("http")) || (scheme == QLatin1StringView("https")) || (scheme == QLatin1StringView("ftp"))
851 || (scheme == QLatin1StringView("file")) || (scheme == QLatin1StringView("ftps")) || (scheme == QLatin1StringView("sftp"))
852 || (scheme == QLatin1StringView("help")) || (scheme == QLatin1StringView("vnc")) || (scheme == QLatin1StringView("smb"))
853 || (scheme == QLatin1StringView("fish")) || (scheme == QLatin1StringView("news")) || (scheme == QLatin1StringView("tel"))
854 || (scheme == QLatin1StringView("geo")) || (scheme == QLatin1StringView("sms"))) {
855 PimCommon::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL..."));
856 QTimer::singleShot(2s, PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::reset);
857
858 QMimeDatabase mimeDb;
859 auto mime = mimeDb.mimeTypeForUrl(url);
860 if (mime.name() == QLatin1StringView("application/x-desktop") || mime.name() == QLatin1StringView("application/x-executable")
861 || mime.name() == QLatin1StringView("application/x-ms-dos-executable") || mime.name() == QLatin1StringView("application/x-shellscript")) {
863 nullptr,
864 xi18nc("@info", "Do you really want to execute <filename>%1</filename>?", url.toDisplayString(QUrl::PreferLocalFile)),
865 QString(),
866 KGuiItem(i18nc("@action:button", "Execute")),
868 != KMessageBox::ButtonCode::PrimaryAction) {
869 return true;
870 }
871 }
872 w->checkPhishingUrl();
873 return true;
874 } else {
875 return false;
876 }
877}
878
879bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
880{
881 Q_UNUSED(url)
882 Q_UNUSED(window)
883 return false;
884}
885
886bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
887{
888 Q_UNUSED(window)
889 return url.scheme() == QLatin1StringView("cid");
890}
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()
bool bodyIsMessage() const
QList< Content * > contents()
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)
KGuiItem cancel()
const QList< QKeySequence > & end()
const QList< QKeySequence > & copy()
const QList< QKeySequence > & open()
void push_back(QByteArrayView str)
void setText(const QString &text, Mode mode)
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 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 Dec 6 2024 11:58:49 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.