File indexing completed on 2025-03-09 04:54:40

0001 /*
0002    SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "mailwebengineview.h"
0007 #include "../urlhandlermanager.h"
0008 #include "cidreferencesurlinterceptor/cidreferencesurlinterceptor.h"
0009 #include "cidschemehandler/cidschemehandler.h"
0010 #include "loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h"
0011 #include "mailwebenginepage.h"
0012 #include "messageviewer/messageviewersettings.h"
0013 #include "webengineviewer/webengineaccesskey.h"
0014 #include "webengineviewer/webenginescript.h"
0015 #include <WebEngineViewer/BlockExternalResourcesUrlInterceptor>
0016 #include <WebEngineViewer/InterceptorManager>
0017 #include <WebEngineViewer/WebEngineManageScript>
0018 
0019 #include "scamdetection/scamdetectionwebengine.h"
0020 #include <QContextMenuEvent>
0021 #include <QWebEngineProfile>
0022 #include <WebEngineViewer/WebHitTest>
0023 
0024 #include <QPainter>
0025 #include <QPrinter>
0026 #include <QWebEngineUrlScheme>
0027 
0028 #include <WebEngineViewer/WebHitTestResult>
0029 
0030 using namespace MessageViewer;
0031 template<typename Arg, typename R, typename C>
0032 struct InvokeWrapper {
0033     R *receiver;
0034     void (C::*memberFunction)(Arg);
0035     void operator()(Arg result)
0036     {
0037         (receiver->*memberFunction)(result);
0038     }
0039 };
0040 
0041 template<typename Arg, typename R, typename C>
0042 
0043 InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
0044 {
0045     InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
0046     return wrapper;
0047 }
0048 
0049 class MessageViewer::MailWebEngineViewPrivate
0050 {
0051 public:
0052     MailWebEngineViewPrivate() = default;
0053 
0054     QUrl mHoveredUrl;
0055     QPoint mLastClickPosition;
0056     ScamDetectionWebEngine *mScamDetection = nullptr;
0057     WebEngineViewer::WebEngineAccessKey *mWebViewAccessKey = nullptr;
0058     MessageViewer::LoadExternalReferencesUrlInterceptor *mExternalReference = nullptr;
0059     MailWebEnginePage *mPageEngine = nullptr;
0060     WebEngineViewer::InterceptorManager *mNetworkAccessManager = nullptr;
0061     MessageViewer::ViewerPrivate *mViewer = nullptr;
0062     WebEngineViewer::BlockTrackingUrlInterceptor *mBlockMailTrackingUrl = nullptr;
0063     bool mCanStartDrag = false;
0064 };
0065 
0066 MailWebEngineView::MailWebEngineView(KActionCollection *ac, QWidget *parent)
0067     : WebEngineViewer::WebEngineView(parent)
0068     , d(new MessageViewer::MailWebEngineViewPrivate)
0069 {
0070     d->mPageEngine = new MailWebEnginePage(this);
0071     setPage(d->mPageEngine);
0072     d->mWebViewAccessKey = new WebEngineViewer::WebEngineAccessKey(this, this);
0073     d->mWebViewAccessKey->setActionCollection(ac);
0074     d->mScamDetection = new ScamDetectionWebEngine(this);
0075     connect(d->mScamDetection, &ScamDetectionWebEngine::messageMayBeAScam, this, &MailWebEngineView::messageMayBeAScam);
0076     connect(d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::openUrl, this, &MailWebEngineView::openUrl);
0077     connect(this, &MailWebEngineView::loadFinished, this, &MailWebEngineView::slotLoadFinished);
0078 
0079     d->mPageEngine->profile()->installUrlSchemeHandler(QByteArrayLiteral("cid"), new CidSchemeHandler(this));
0080 
0081     d->mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this);
0082     d->mExternalReference = new MessageViewer::LoadExternalReferencesUrlInterceptor(this);
0083     connect(d->mExternalReference, &MessageViewer::LoadExternalReferencesUrlInterceptor::urlBlocked, this, &MailWebEngineView::urlBlocked);
0084     d->mNetworkAccessManager->addInterceptor(d->mExternalReference);
0085     auto cidReference = new MessageViewer::CidReferencesUrlInterceptor(this);
0086     d->mNetworkAccessManager->addInterceptor(cidReference);
0087     auto blockExternalUrl = new WebEngineViewer::BlockExternalResourcesUrlInterceptor(this);
0088     connect(blockExternalUrl, &WebEngineViewer::BlockExternalResourcesUrlInterceptor::formSubmittedForbidden, this, &MailWebEngineView::formSubmittedForbidden);
0089     d->mNetworkAccessManager->addInterceptor(blockExternalUrl);
0090 
0091     d->mBlockMailTrackingUrl = new WebEngineViewer::BlockTrackingUrlInterceptor(this);
0092     connect(d->mBlockMailTrackingUrl, &WebEngineViewer::BlockTrackingUrlInterceptor::trackingFound, this, &MailWebEngineView::mailTrackingFound);
0093     d->mNetworkAccessManager->addInterceptor(d->mBlockMailTrackingUrl);
0094 
0095     setFocusPolicy(Qt::WheelFocus);
0096     connect(d->mPageEngine, &MailWebEnginePage::urlClicked, this, &MailWebEngineView::openUrl);
0097     connect(page(), &QWebEnginePage::scrollPositionChanged, d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::hideAccessKeys);
0098 }
0099 
0100 MailWebEngineView::~MailWebEngineView() = default;
0101 
0102 void MailWebEngineView::readConfig()
0103 {
0104     d->mBlockMailTrackingUrl->setEnabledMailTrackingInterceptor(MessageViewer::MessageViewerSettings::self()->mailTrackingUrlEnabled());
0105 }
0106 
0107 void MailWebEngineView::setLinkHovered(const QUrl &url)
0108 {
0109     // TODO we need to detect image url too.
0110     d->mHoveredUrl = url;
0111 }
0112 
0113 void MailWebEngineView::runJavaScriptInWordId(const QString &script)
0114 {
0115     page()->runJavaScript(script, WebEngineViewer::WebEngineManageScript::scriptWordId());
0116 }
0117 
0118 void MailWebEngineView::setViewer(MessageViewer::ViewerPrivate *viewer)
0119 {
0120     d->mViewer = viewer;
0121 }
0122 
0123 void MailWebEngineView::contextMenuEvent(QContextMenuEvent *e)
0124 {
0125     WebEngineViewer::WebHitTest *webHit = d->mPageEngine->hitTestContent(e->pos());
0126     connect(webHit, &WebEngineViewer::WebHitTest::finished, this, &MailWebEngineView::slotWebHitFinished);
0127 }
0128 
0129 void MailWebEngineView::slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result)
0130 {
0131     Q_EMIT popupMenu(result);
0132 }
0133 
0134 void MailWebEngineView::scrollUp(int pixels)
0135 {
0136     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollUp(pixels));
0137 }
0138 
0139 void MailWebEngineView::scrollDown(int pixels)
0140 {
0141     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollDown(pixels));
0142 }
0143 
0144 void MailWebEngineView::selectAll()
0145 {
0146     page()->triggerAction(QWebEnginePage::SelectAll);
0147 }
0148 
0149 void MailWebEngineView::slotZoomChanged(qreal zoom)
0150 {
0151     setZoomFactor(zoom);
0152 }
0153 
0154 void MailWebEngineView::scamCheck()
0155 {
0156     d->mScamDetection->scanPage(page());
0157 }
0158 
0159 void MailWebEngineView::slotShowDetails()
0160 {
0161     d->mScamDetection->showDetails();
0162 }
0163 
0164 void MailWebEngineView::forwardKeyReleaseEvent(QKeyEvent *e)
0165 {
0166     if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
0167         d->mWebViewAccessKey->keyReleaseEvent(e);
0168     }
0169 }
0170 
0171 void MailWebEngineView::forwardMousePressEvent(QMouseEvent *event)
0172 {
0173     if (d->mViewer && !d->mHoveredUrl.isEmpty()) {
0174         if (event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier)) {
0175             // special processing for shift+click
0176             if (URLHandlerManager::instance()->handleShiftClick(d->mHoveredUrl, d->mViewer)) {
0177                 event->accept();
0178                 return;
0179             }
0180         }
0181         if (event->button() == Qt::LeftButton) {
0182             d->mCanStartDrag = URLHandlerManager::instance()->willHandleDrag(d->mHoveredUrl, d->mViewer);
0183             d->mLastClickPosition = event->pos();
0184         }
0185     }
0186 }
0187 
0188 void MailWebEngineView::forwardMouseMoveEvent(QMouseEvent *event)
0189 {
0190     if (d->mViewer && !d->mHoveredUrl.isEmpty()) {
0191         // If we are potentially handling a drag, deal with that.
0192         if (d->mCanStartDrag && (event->buttons() & Qt::LeftButton)) {
0193             if ((d->mLastClickPosition - event->pos()).manhattanLength() > QApplication::startDragDistance()) {
0194                 if (URLHandlerManager::instance()->handleDrag(d->mHoveredUrl, d->mViewer)) {
0195                     // If the URL handler manager started a drag, don't handle this in the future
0196                     d->mCanStartDrag = false;
0197                 }
0198             }
0199             event->accept();
0200         }
0201     }
0202 }
0203 
0204 void MailWebEngineView::forwardMouseReleaseEvent(QMouseEvent *event)
0205 {
0206     Q_UNUSED(event)
0207     d->mCanStartDrag = false;
0208 }
0209 
0210 void MailWebEngineView::forwardKeyPressEvent(QKeyEvent *e)
0211 {
0212     if (e && hasFocus()) {
0213         if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
0214             d->mWebViewAccessKey->keyPressEvent(e);
0215         }
0216     }
0217 }
0218 
0219 void MailWebEngineView::forwardWheelEvent(QWheelEvent *e)
0220 {
0221     if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
0222         d->mWebViewAccessKey->wheelEvent(e);
0223     }
0224     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
0225         const int numDegrees = e->angleDelta().y() / 8;
0226         const int numSteps = numDegrees / 15;
0227         Q_EMIT wheelZoomChanged(numSteps);
0228         e->accept();
0229     }
0230 }
0231 
0232 void MailWebEngineView::resizeEvent(QResizeEvent *e)
0233 {
0234     if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
0235         d->mWebViewAccessKey->resizeEvent(e);
0236     }
0237     QWebEngineView::resizeEvent(e);
0238 }
0239 
0240 void MailWebEngineView::saveMainFrameScreenshotInFile(const QString &filename)
0241 {
0242     // TODO need to verify it
0243     QImage image(size(), QImage::Format_ARGB32_Premultiplied);
0244     image.fill(Qt::transparent);
0245 
0246     QPainter painter(&image);
0247     painter.setRenderHint(QPainter::Antialiasing, true);
0248     painter.setRenderHint(QPainter::TextAntialiasing, true);
0249     painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
0250     render(&painter);
0251     painter.end();
0252     image.save(filename);
0253 }
0254 
0255 void MailWebEngineView::showAccessKeys()
0256 {
0257     d->mWebViewAccessKey->showAccessKeys();
0258 }
0259 
0260 void MailWebEngineView::hideAccessKeys()
0261 {
0262     d->mWebViewAccessKey->hideAccessKeys();
0263 }
0264 
0265 void MailWebEngineView::isScrolledToBottom()
0266 {
0267     page()->runJavaScript(WebEngineViewer::WebEngineScript::isScrolledToBottom(),
0268                           WebEngineViewer::WebEngineManageScript::scriptWordId(),
0269                           invoke(this, &MailWebEngineView::handleIsScrolledToBottom));
0270 }
0271 
0272 void MailWebEngineView::setElementByIdVisible(const QString &id, bool visible)
0273 {
0274     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setElementByIdVisible(id, visible));
0275 }
0276 
0277 void MailWebEngineView::removeAttachmentMarking(const QString &id)
0278 {
0279     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::removeStyleToElement(id));
0280 }
0281 
0282 void MailWebEngineView::markAttachment(const QString &id, const QString &style)
0283 {
0284     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setStyleToElement(id, style));
0285 }
0286 
0287 void MailWebEngineView::scrollToAnchor(const QString &anchor)
0288 {
0289     page()->runJavaScript(WebEngineViewer::WebEngineScript::searchElementPosition(anchor),
0290                           WebEngineViewer::WebEngineManageScript::scriptWordId(),
0291                           invoke(this, &MailWebEngineView::handleScrollToAnchor));
0292 }
0293 
0294 void MailWebEngineView::handleIsScrolledToBottom(const QVariant &result)
0295 {
0296     bool scrolledToBottomResult = false;
0297     if (result.isValid()) {
0298         scrolledToBottomResult = result.toBool();
0299     }
0300     Q_EMIT pageIsScrolledToBottom(scrolledToBottomResult);
0301 }
0302 
0303 void MailWebEngineView::handleScrollToAnchor(const QVariant &result)
0304 {
0305     if (result.isValid()) {
0306         const QList<QVariant> lst = result.toList();
0307         if (lst.count() == 2) {
0308             const QPoint pos(lst.at(0).toInt(), lst.at(1).toInt());
0309             runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToPosition(pos));
0310         }
0311     }
0312 }
0313 
0314 void MailWebEngineView::scrollPageDown(int percent)
0315 {
0316     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollPercentage(percent));
0317 }
0318 
0319 void MailWebEngineView::scrollPageUp(int percent)
0320 {
0321     scrollPageDown(-percent);
0322 }
0323 
0324 void MailWebEngineView::scrollToRelativePosition(qreal pos)
0325 {
0326     runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToRelativePosition(pos));
0327 }
0328 
0329 void MailWebEngineView::setAllowExternalContent(bool b)
0330 {
0331     if (d->mExternalReference->allowExternalContent() != b) {
0332         d->mExternalReference->setAllowExternalContent(b);
0333         reload();
0334     }
0335 }
0336 
0337 QList<QAction *> MailWebEngineView::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const
0338 {
0339     return d->mNetworkAccessManager->interceptorUrlActions(result);
0340 }
0341 
0342 void MailWebEngineView::slotLoadFinished()
0343 {
0344     scamCheck();
0345 }
0346 
0347 void MailWebEngineView::setPrintElementBackground(bool printElementBackground)
0348 {
0349     d->mPageEngine->setPrintElementBackground(printElementBackground);
0350 }
0351 
0352 void MailWebEngineView::printPreviewPage(QPrinter *printer)
0353 {
0354     print(printer);
0355 }
0356 
0357 void MailWebEngineView::initializeCustomScheme()
0358 {
0359     QWebEngineUrlScheme cidScheme("cid");
0360     cidScheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ContentSecurityPolicyIgnored | QWebEngineUrlScheme::LocalScheme
0361                        | QWebEngineUrlScheme::LocalAccessAllowed);
0362     cidScheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
0363     QWebEngineUrlScheme::registerScheme(cidScheme);
0364 }
0365 
0366 #include "moc_mailwebengineview.cpp"