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"
18#include "urlhandlermanager_p.h"
19#include "utils/mimetype.h"
20#include "viewer/viewer_p.h"
22#include <MimeTreeParser/NodeHelper>
23#include <MimeTreeParser/PartNodeBodyPart>
25#include <Akonadi/OpenEmailAddressJob>
26#include <MessageCore/StringUtil>
27#include <PimCommon/BroadcastStatus>
28#include <PimCommon/PimUtil>
30#include <Akonadi/ContactSearchJob>
32#include <Akonadi/MessageFlags>
33#include <KEmailAddress>
35#include <KMime/Content>
38#include <KLocalizedString>
41#include <QApplication>
48#include <QMimeDatabase>
50#include <QStandardPaths>
56#include <Libkleo/AuditLogEntry>
57#include <Libkleo/AuditLogViewer>
60using namespace std::chrono_literals;
64using namespace MessageViewer;
65using namespace MessageCore;
75BodyPartURLHandlerManager::~BodyPartURLHandlerManager()
78 for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero<Interface::BodyPartURLHandler>());
87 unregisterHandler(handler);
89 auto it = mHandlers.find(mt);
90 if (it == mHandlers.end()) {
91 it = mHandlers.insert(mt, {});
99 auto it = mHandlers.begin();
100 while (it != mHandlers.end()) {
101 it->erase(
remove(it->begin(), it->end(), handler), it->end());
103 it = mHandlers.erase(it);
121 qCDebug(MESSAGEVIEWER_LOG) <<
"BodyPartURLHandler: urlPath ==" << urlPath;
127 if (urlParts.
size() != 3) {
136 return w->nodeFromUrl(
QUrl(urlParts.
at(1)));
145 if (mimeType ==
"text/x-vcard") {
148 return mHandlers.value(mimeType);
155bool BodyPartURLHandlerManager::handleClick(
const QUrl &url, ViewerPrivate *w)
const
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)) {
176bool BodyPartURLHandlerManager::handleContextMenuRequest(
const QUrl &url,
const QPoint &p, ViewerPrivate *w)
const
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)) {
196QString BodyPartURLHandlerManager::statusBarMessage(
const QUrl &url, ViewerPrivate *w)
const
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);
223URLHandlerManager::URLHandlerManager()
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());
239URLHandlerManager::~URLHandlerManager()
241 for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero<MimeTreeParser::URLHandler>());
247 self =
new URLHandlerManager();
252void URLHandlerManager::registerHandler(
const MimeTreeParser::URLHandler *handler)
257 unregisterHandler(handler);
258 mHandlers.push_back(handler);
261void URLHandlerManager::unregisterHandler(
const MimeTreeParser::URLHandler *handler)
264 mHandlers.erase(
remove(mHandlers.begin(), mHandlers.end(), handler), mHandlers.end());
269 if (mBodyPartURLHandlerManager) {
270 mBodyPartURLHandlerManager->registerHandler(handler, mimeType);
276 if (mBodyPartURLHandlerManager) {
277 mBodyPartURLHandlerManager->unregisterHandler(handler);
281bool URLHandlerManager::handleClick(
const QUrl &url, ViewerPrivate *w)
const
283 HandlerList::const_iterator
end(mHandlers.constEnd());
284 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
285 if ((*it)->handleClick(url, w)) {
292bool URLHandlerManager::handleShiftClick(
const QUrl &url, ViewerPrivate *window)
const
294 HandlerList::const_iterator
end(mHandlers.constEnd());
295 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
296 if ((*it)->handleShiftClick(url, window)) {
303bool URLHandlerManager::willHandleDrag(
const QUrl &url, ViewerPrivate *window)
const
305 HandlerList::const_iterator
end(mHandlers.constEnd());
307 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
308 if ((*it)->willHandleDrag(url, window)) {
315bool URLHandlerManager::handleDrag(
const QUrl &url, ViewerPrivate *window)
const
317 HandlerList::const_iterator
end(mHandlers.constEnd());
318 for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
319 if ((*it)->handleDrag(url, window)) {
326bool URLHandlerManager::handleContextMenuRequest(
const QUrl &url,
const QPoint &p, ViewerPrivate *w)
const
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)) {
337QString URLHandlerManager::statusBarMessage(
const QUrl &url, ViewerPrivate *w)
const
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);
355bool KMailProtocolURLHandler::handleClick(
const QUrl &url, ViewerPrivate *w)
const
357 if (url.
scheme() == QLatin1StringView(
"kmail")) {
361 const QString urlPath(url.
path());
362 if (urlPath == QLatin1StringView(
"showHTML")) {
363 w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html);
364 w->update(MimeTreeParser::Force);
366 }
else if (urlPath == QLatin1StringView(
"goOnline")) {
369 }
else if (urlPath == QLatin1StringView(
"goResourceOnline")) {
370 w->goResourceOnline();
372 }
else if (urlPath == QLatin1StringView(
"loadExternal")) {
373 w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride());
374 w->update(MimeTreeParser::Force);
376 }
else if (urlPath == QLatin1StringView(
"decryptMessage")) {
377 w->setDecryptMessageOverwrite(
true);
378 w->update(MimeTreeParser::Force);
380 }
else if (urlPath == QLatin1StringView(
"showSignatureDetails")) {
381 w->setShowSignatureDetails(
true);
382 w->update(MimeTreeParser::Force);
384 }
else if (urlPath == QLatin1StringView(
"hideSignatureDetails")) {
385 w->setShowSignatureDetails(
false);
386 w->update(MimeTreeParser::Force);
388 }
else if (urlPath == QLatin1StringView(
"showEncryptionDetails")) {
389 w->setShowEncryptionDetails(
true);
390 w->update(MimeTreeParser::Force);
392 }
else if (urlPath == QLatin1StringView(
"hideEncryptionDetails")) {
393 w->setShowEncryptionDetails(
false);
394 w->update(MimeTreeParser::Force);
401QString KMailProtocolURLHandler::statusBarMessage(
const QUrl &url, ViewerPrivate *)
const
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.");
427 }
else if (schemeStr == QLatin1StringView(
"help")) {
428 return i18n(
"Open Documentation");
433bool ExpandCollapseQuoteURLManager::handleClick(
const QUrl &url, ViewerPrivate *w)
const
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);
442 w->slotLevelQuote(levelQuote);
449bool ExpandCollapseQuoteURLManager::handleDrag(
const QUrl &url, ViewerPrivate *window)
const
456QString ExpandCollapseQuoteURLManager::statusBarMessage(
const QUrl &url, ViewerPrivate *)
const
458 if (url.
scheme() == QLatin1StringView(
"kmail") && url.
path() == QLatin1StringView(
"levelquote")) {
461 if (query[0] == QLatin1Char(
'-')) {
462 return i18n(
"Expand all quoted text.");
464 return i18n(
"Collapse quoted text.");
471bool SMimeURLHandler::handleClick(
const QUrl &url, ViewerPrivate *w)
const
474 if (url.
scheme() == QLatin1StringView(
"key")) {
483 lst << QStringLiteral(
"--parent-windowid") <<
QString::number(
static_cast<qlonglong
>(w->viewer()->mainWindow()->winId())) << QStringLiteral(
"--query")
486 const QString exec = PimCommon::Util::findExecutable(QStringLiteral(
"kleopatra"));
489 qCWarning(MESSAGEVIEWER_LOG) <<
"Could not find kleopatra executable in PATH";
491 i18n(
"Could not start certificate manager. "
492 "Please check your installation."),
493 i18n(
"KMail Error"));
500QString SMimeURLHandler::statusBarMessage(
const QUrl &url, ViewerPrivate *)
const
503 if (url.
scheme() == QLatin1StringView(
"key")) {
510 return i18n(
"Show certificate 0x%1", keyId);
513bool HtmlAnchorHandler::handleClick(
const QUrl &url, ViewerPrivate *w)
const
523QString MailToURLHandler::statusBarMessage(
const QUrl &url, ViewerPrivate *)
const
525 if (url.
scheme() == QLatin1StringView(
"mailto")) {
531static QString searchFullEmailByUid(
const QString &uid)
534 auto job =
new Akonadi::ContactSearchJob();
539 if (!res.isEmpty()) {
540 KContacts::Addressee addr = res.at(0);
546static void runKAddressBook(
const QUrl &url)
548 auto job =
new Akonadi::OpenEmailAddressJob(url.
path(),
nullptr);
552bool ContactUidURLHandler::handleClick(
const QUrl &url, ViewerPrivate *)
const
554 if (url.
scheme() == QLatin1StringView(
"uid")) {
555 runKAddressBook(url);
562bool ContactUidURLHandler::handleContextMenuRequest(
const QUrl &url,
const QPoint &p, ViewerPrivate *)
const
570#ifndef QT_NO_CLIPBOARD
574 QAction *a = menu.
exec(p);
576 runKAddressBook(url);
577#ifndef QT_NO_CLIPBOARD
578 }
else if (a == copy) {
579 const QString fullEmail = searchFullEmailByUid(url.
path());
584 PimCommon::BroadcastStatus::instance()->setStatusMsg(
i18n(
"Address copied to clipboard."));
592QString ContactUidURLHandler::statusBarMessage(
const QUrl &url, ViewerPrivate *)
const
594 if (url.
scheme() == QLatin1StringView(
"uid")) {
595 return i18n(
"Lookup the contact in KAddressbook");
601KMime::Content *AttachmentURLHandler::nodeForUrl(
const QUrl &url, ViewerPrivate *w)
const
603 if (!w || !w->mMessage) {
606 if (url.
scheme() == QLatin1StringView(
"attachment")) {
607 KMime::Content *node = w->nodeFromUrl(url);
613bool AttachmentURLHandler::attachmentIsInHeader(
const QUrl &url)
const
615 bool inHeader =
false;
616 QUrlQuery
query(url);
617 const QString place =
query.queryItemValue(QStringLiteral(
"place")).toLower();
619 inHeader = (place == QLatin1StringView(
"header"));
624bool AttachmentURLHandler::handleClick(
const QUrl &url, ViewerPrivate *w)
const
626 KMime::Content *node = nodeForUrl(url, w);
630 const bool inHeader = attachmentIsInHeader(url);
631 const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
633 w->scrollToAttachment(node);
636 w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
642bool AttachmentURLHandler::handleShiftClick(
const QUrl &url, ViewerPrivate *window)
const
644 KMime::Content *node = nodeForUrl(url, window);
656 if (isEncapsulatedMessage) {
666 window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << newUrl);
671 window->viewer()->showOpenAttachmentFolderWidget(urlList);
678bool AttachmentURLHandler::willHandleDrag(
const QUrl &url, ViewerPrivate *window)
const
680 return nodeForUrl(url, window) !=
nullptr;
683bool AttachmentURLHandler::handleDrag(
const QUrl &url, ViewerPrivate *window)
const
685#ifndef QT_NO_DRAGANDDROP
686 KMime::Content *node = nodeForUrl(url, window);
696 if (isEncapsulatedMessage) {
704 fileName =
window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
709 if (!mbox.
load(fileName)) {
710 qCWarning(MESSAGEVIEWER_LOG) <<
"MBOX: Impossible to open file";
716 qCWarning(MESSAGEVIEWER_LOG) <<
"MBOX: Impossible to save file";
721 if (node->
header<KMime::Headers::Subject>()) {
723 node = node->
contents().constLast();
724 fileName =
window->nodeHelper()->writeNodeToTempFile(node);
729 tUrl =
window->nodeHelper()->tempFileUrlFromNode(node);
730 fileName = tUrl.
path();
737 auto drag =
new QDrag(
window->viewer());
738 auto mimeData =
new QMimeData();
739 mimeData->setUrls(QList<QUrl>() << tUrl);
740 drag->setMimeData(mimeData);
752bool AttachmentURLHandler::handleContextMenuRequest(
const QUrl &url,
const QPoint &p, ViewerPrivate *w)
const
754 KMime::Content *node = nodeForUrl(url, w);
759 w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
763QString AttachmentURLHandler::statusBarMessage(
const QUrl &url, ViewerPrivate *w)
const
765 KMime::Content *node = nodeForUrl(url, w);
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());
776 return i18n(
"Encapsulated Message");
779 return i18n(
"Unnamed attachment");
782static QString extractAuditLog(
const QUrl &url)
784 if (url.
scheme() != QLatin1StringView(
"kmail") || url.
path() != QLatin1StringView(
"showAuditLog")) {
787 QUrlQuery
query(url);
788 Q_ASSERT(!
query.queryItemValue(QStringLiteral(
"log")).isEmpty());
789 return query.queryItemValue(QStringLiteral(
"log"));
792bool ShowAuditLogURLHandler::handleClick(
const QUrl &url, ViewerPrivate *w)
const
794 const QString auditLog = extractAuditLog(url);
798 Kleo::AuditLogViewer::showAuditLog(w->mMainWindow, Kleo::AuditLogEntry{auditLog, GpgME::Error{}}, auditLog);
802bool ShowAuditLogURLHandler::handleContextMenuRequest(
const QUrl &url,
const QPoint &, ViewerPrivate *w)
const
806 return !extractAuditLog(url).
isEmpty();
809QString ShowAuditLogURLHandler::statusBarMessage(
const QUrl &url, ViewerPrivate *)
const
811 if (extractAuditLog(url).isEmpty()) {
814 return i18n(
"Show GnuPG Audit Log for this operation");
818bool ShowAuditLogURLHandler::handleDrag(
const QUrl &url, ViewerPrivate *window)
const
825bool InternalImageURLHandler::handleDrag(
const QUrl &url, ViewerPrivate *window)
const
835bool InternalImageURLHandler::willHandleDrag(
const QUrl &url, ViewerPrivate *window)
const
838 if (url.
scheme() == QLatin1StringView(
"data") && url.
path().
startsWith(QLatin1StringView(
"image"))) {
842 const QString imagePath =
847bool KRunURLHandler::handleClick(
const QUrl &url, ViewerPrivate *w)
const
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);
858 QMimeDatabase mimeDb;
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")) {
866 KGuiItem(
i18nc(
"@action:button",
"Execute")),
868 != KMessageBox::ButtonCode::PrimaryAction) {
872 w->checkPhishingUrl();
879bool EmbeddedImageURLHandler::handleDrag(
const QUrl &url, ViewerPrivate *window)
const
886bool EmbeddedImageURLHandler::willHandleDrag(
const QUrl &url, ViewerPrivate *window)
const
889 return url.
scheme() == QLatin1StringView(
"cid");
void setMimeType(const QString &mimeType)
void setPayload(const T &p)
bool load(const QString &fileName)
MBoxEntry appendMessage(const KMime::Message::Ptr &message)
bool save(const QString &fileName=QString())
const Headers::ContentType * contentType() const
QSharedPointer< Message > bodyAsMessage()
bool bodyIsMessage() const
QList< KMime::Content * > List
QList< Content * > contents()
static QString mimeType()
QSharedPointer< Message > Ptr
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
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)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT CopyJob * copy(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
QString name(const QVariant &location)
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)
void push_back(QByteArrayView str)
void setText(const QString &text, Mode mode)
QIcon fromTheme(const QString &name)
const_reference at(qsizetype i) const const
qsizetype length() const const
qsizetype size() const const
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
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