scamdetectionwebengine.cpp 7.88 KB
Newer Older
1 2 3
/*
  Copyright (c) 2016 Montel Laurent <montel@kde.org>

Laurent Montel's avatar
Laurent Montel committed
4 5 6 7 8 9 10 11 12
  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
  License for more details.
13

Laurent Montel's avatar
Laurent Montel committed
14 15 16 17 18 19
  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to the
  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.

*/
20
#include "scamdetectionwebengine.h"
21 22
#include "scamcheckshorturlmanager.h"
#include "scamdetectiondetailsdialog.h"
Laurent Montel's avatar
Laurent Montel committed
23
#include "settings/messageviewersettings.h"
Laurent Montel's avatar
Laurent Montel committed
24
#include "MessageViewer/ScamCheckShortUrl"
Laurent Montel's avatar
Laurent Montel committed
25
#include "webengineviewer/webenginescript.h"
26
#include <WebEngineViewer/WebEngineManageScript>
Laurent Montel's avatar
Laurent Montel committed
27 28

#include <KLocalizedString>
29

Laurent Montel's avatar
Laurent Montel committed
30
#include <QRegularExpression>
31
#include <QPointer>
Laurent Montel's avatar
Laurent Montel committed
32
#include <QWebEnginePage>
33 34

using namespace MessageViewer;
Laurent Montel's avatar
Laurent Montel committed
35 36 37

template<typename Arg, typename R, typename C>
struct InvokeWrapper {
38
    QPointer<R> receiver;
Laurent Montel's avatar
Laurent Montel committed
39 40 41
    void (C::*memberFunction)(Arg);
    void operator()(Arg result)
    {
42 43 44
        if (receiver) {
            (receiver->*memberFunction)(result);
        }
Laurent Montel's avatar
Laurent Montel committed
45 46 47 48 49 50 51 52 53 54 55
    }
};

template<typename Arg, typename R, typename C>

InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
    InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
    return wrapper;
}

Laurent Montel's avatar
Laurent Montel committed
56 57 58 59 60
static QString addWarningColor(const QString &url)
{
    const QString error = QStringLiteral("<font color=#FF0000>%1</font>").arg(url);
    return error;
}
61 62 63 64 65 66 67 68

class MessageViewer::ScamDetectionWebEnginePrivate
{
public:
    ScamDetectionWebEnginePrivate()
    {

    }
69 70
    QString mDetails;
    QPointer<MessageViewer::ScamDetectionDetailsDialog> mDetailsDialog;
71 72 73 74 75 76 77 78 79 80 81 82 83
};

ScamDetectionWebEngine::ScamDetectionWebEngine(QObject *parent)
    : QObject(parent),
      d(new MessageViewer::ScamDetectionWebEnginePrivate)
{

}

ScamDetectionWebEngine::~ScamDetectionWebEngine()
{
    delete d;
}
84

Laurent Montel's avatar
Laurent Montel committed
85 86 87
void ScamDetectionWebEngine::scanPage(QWebEnginePage *page)
{
    if (MessageViewer::MessageViewerSettings::self()->scamDetectionEnabled()) {
88 89 90 91 92
#if QT_VERSION >= 0x050700
        page->runJavaScript(WebEngineViewer::WebEngineScript::findAllAnchorsAndForms(),
                            WebEngineViewer::WebEngineManageScript::scriptWordId(),
                            invoke(this, &ScamDetectionWebEngine::handleScanPage));
#else
Laurent Montel's avatar
Laurent Montel committed
93
        page->runJavaScript(WebEngineViewer::WebEngineScript::findAllAnchorsAndForms(), invoke(this, &ScamDetectionWebEngine::handleScanPage));
94
#endif
Laurent Montel's avatar
Laurent Montel committed
95 96 97 98 99 100
    }
}

void ScamDetectionWebEngine::handleScanPage(const QVariant &result)
{
    bool foundScam = false;
Laurent Montel's avatar
Laurent Montel committed
101

Laurent Montel's avatar
Laurent Montel committed
102
    d->mDetails.clear();
Laurent Montel's avatar
Laurent Montel committed
103 104 105 106 107
    const QVariantList resultList = result.toList();
    if (resultList.count() != 1) {
        Q_EMIT resultScanDetection(foundScam);
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
108
    d->mDetails = QLatin1String("<b>") + i18n("Details:") + QLatin1String("</b><ul>");
109
    QRegularExpression ip4regExp(QStringLiteral("\\b[0-9]{1,3}\\.[0-9]{1,3}(?:\\.[0-9]{0,3})?(?:\\.[0-9]{0,3})?"));
Laurent Montel's avatar
Laurent Montel committed
110 111
    const QVariantMap mapResult = resultList.at(0).toMap();
    const QList<QVariant> lst = mapResult.value(QStringLiteral("anchors")).toList();
Laurent Montel's avatar
Laurent Montel committed
112
    Q_FOREACH (const QVariant &var, lst) {
Laurent Montel's avatar
Laurent Montel committed
113 114 115
        QMap<QString, QVariant> mapVariant = var.toMap();
        //qDebug()<<" mapVariant"<<mapVariant;

116
        //1) detect if title has a url and title != href
Laurent Montel's avatar
Laurent Montel committed
117 118
        const QString title = mapVariant.value(QStringLiteral("title")).toString();
        const QString href = mapVariant.value(QStringLiteral("src")).toString();
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
        const QUrl url(href);
        if (!title.isEmpty()) {
            if (title.startsWith(QStringLiteral("http:"))
                    || title.startsWith(QStringLiteral("https:"))
                    || title.startsWith(QStringLiteral("www."))) {
                if (title.startsWith(QStringLiteral("www."))) {
                    const QString completUrl =  url.scheme() + QLatin1String("://") + title;
                    if (completUrl != href &&
                            href != (completUrl + QLatin1Char('/'))) {
                        foundScam = true;
                    }
                } else {
                    if (href != title) {
                        // http://www.kde.org == http://www.kde.org/
                        if (href != (title + QLatin1Char('/'))) {
                            foundScam = true;
                        }
                    }
                }
                if (foundScam) {
Laurent Montel's avatar
Laurent Montel committed
139
                    d->mDetails += QLatin1String("<li>") + i18n("This email contains a link which reads as '%1' in the text, but actually points to '%2'. This is often the case in scam emails to mislead the recipient", addWarningColor(title), addWarningColor(href)) + QLatin1String("</li>");
140 141 142 143 144 145 146
                }
            }
        }
        if (!foundScam) {
            //2) detect if url href has ip and not server name.
            const QString hostname = url.host();
            if (hostname.contains(ip4regExp) && !hostname.contains(QStringLiteral("127.0.0.1"))) { //hostname
Laurent Montel's avatar
Laurent Montel committed
147
                d->mDetails += QLatin1String("<li>") + i18n("This email contains a link which points to a numerical IP address (%1) instead of a typical textual website address. This is often the case in scam emails.", addWarningColor(hostname)) + QLatin1String("</li>");
148 149
                foundScam = true;
            } else if (hostname.contains(QLatin1Char('%'))) { //Hexa value for ip
Laurent Montel's avatar
Laurent Montel committed
150
                d->mDetails += QLatin1String("<li>") + i18n("This email contains a link which points to a hexadecimal IP address (%1) instead of a typical textual website address. This is often the case in scam emails.", addWarningColor(hostname)) + QLatin1String("</li>");
151 152
                foundScam = true;
            } else if (url.toString().contains(QStringLiteral("url?q="))) { //4) redirect url.
Laurent Montel's avatar
Laurent Montel committed
153
                d->mDetails += QLatin1String("<li>") + i18n("This email contains a link (%1) which has a redirection", addWarningColor(url.toString())) + QLatin1String("</li>");
154 155 156 157
                foundScam = true;
            } else if ((url.toString().count(QStringLiteral("http://")) > 1) ||
                       (url.toString().count(QStringLiteral("https://")) > 1)) { //5) more that 1 http in url.
                if (!url.toString().contains(QStringLiteral("kmail:showAuditLog"))) {
Laurent Montel's avatar
Laurent Montel committed
158
                    d->mDetails += QLatin1String("<li>") + i18n("This email contains a link (%1) which contains multiple http://. This is often the case in scam emails.", addWarningColor(url.toString())) + QLatin1String("</li>");
159 160 161 162 163 164 165
                    foundScam = true;
                }
            }
        }
        //Check shortUrl
        if (!foundScam) {
            if (ScamCheckShortUrl::isShortUrl(url)) {
Laurent Montel's avatar
Laurent Montel committed
166
                d->mDetails += QLatin1String("<li>") + i18n("This email contains a shorturl (%1). It can redirect to another server.", addWarningColor(url.toString())) + QLatin1String("</li>");
167 168 169 170
                foundScam = true;
            }
        }
    }
Laurent Montel's avatar
Laurent Montel committed
171
    if (mapResult.value(QStringLiteral("forms")).toInt() > 0) {
Laurent Montel's avatar
Laurent Montel committed
172
        d->mDetails += QLatin1String("<li></b>") + i18n("Message contains form element. This is often the case in scam emails.") + QLatin1String("</b></li>");
173 174
        foundScam = true;
    }
Laurent Montel's avatar
Laurent Montel committed
175
    d->mDetails += QLatin1String("</ul>");
Laurent Montel's avatar
Laurent Montel committed
176
    //qDebug()<<" d->mDetails "<< d->mDetails;
Laurent Montel's avatar
Laurent Montel committed
177 178 179
    if (foundScam) {
        Q_EMIT messageMayBeAScam();
    }
180
    Q_EMIT resultScanDetection(foundScam);
Laurent Montel's avatar
Laurent Montel committed
181
}
182 183 184 185 186 187 188 189 190

void ScamDetectionWebEngine::showDetails()
{
    if (!d->mDetailsDialog) {
        d->mDetailsDialog = new MessageViewer::ScamDetectionDetailsDialog;
    }
    d->mDetailsDialog->setDetails(d->mDetails);
    d->mDetailsDialog->show();
}