scamdetectionwebengine.cpp 7.39 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
  Copyright (c) 2016 Montel Laurent <montel@kde.org>

  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License, version 2, as
  published by the Free Software Foundation.

  This program 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
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "scamdetectionwebengine.h"
19 20
#include "scamcheckshorturlmanager.h"
#include "scamdetectiondetailsdialog.h"
Laurent Montel's avatar
Laurent Montel committed
21
#include "settings/messageviewersettings.h"
Laurent Montel's avatar
Laurent Montel committed
22
#include "MessageViewer/ScamCheckShortUrl"
Laurent Montel's avatar
Laurent Montel committed
23
#include "webengineviewer/webenginescript.h"
Laurent Montel's avatar
Laurent Montel committed
24 25

#include <KLocalizedString>
26

Laurent Montel's avatar
Laurent Montel committed
27
#include <QRegularExpression>
28
#include <QPointer>
Laurent Montel's avatar
Laurent Montel committed
29
#include <QWebEnginePage>
30 31

using namespace MessageViewer;
Laurent Montel's avatar
Laurent Montel committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

template<typename Arg, typename R, typename C>
struct InvokeWrapper {
    R *receiver;
    void (C::*memberFunction)(Arg);
    void operator()(Arg result)
    {
        (receiver->*memberFunction)(result);
    }
};

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
51 52 53 54 55
static QString addWarningColor(const QString &url)
{
    const QString error = QStringLiteral("<font color=#FF0000>%1</font>").arg(url);
    return error;
}
56 57 58 59 60 61 62 63

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

    }
64 65
    QString mDetails;
    QPointer<MessageViewer::ScamDetectionDetailsDialog> mDetailsDialog;
66 67 68 69 70 71 72 73 74 75 76 77 78
};

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

}

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

Laurent Montel's avatar
Laurent Montel committed
80 81 82
void ScamDetectionWebEngine::scanPage(QWebEnginePage *page)
{
    if (MessageViewer::MessageViewerSettings::self()->scamDetectionEnabled()) {
Laurent Montel's avatar
Laurent Montel committed
83
        page->runJavaScript(WebEngineViewer::WebEngineScript::findAllAnchorsAndForms(), invoke(this, &ScamDetectionWebEngine::handleScanPage));
Laurent Montel's avatar
Laurent Montel committed
84 85 86 87 88 89
    }
}

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

Laurent Montel's avatar
Laurent Montel committed
91
    d->mDetails.clear();
Laurent Montel's avatar
Laurent Montel committed
92 93 94 95 96
    const QVariantList resultList = result.toList();
    if (resultList.count() != 1) {
        Q_EMIT resultScanDetection(foundScam);
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
97
    d->mDetails = QLatin1String("<b>") + i18n("Details:") + QLatin1String("</b><ul>");
98
    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
99 100
    const QVariantMap mapResult = resultList.at(0).toMap();
    const QList<QVariant> lst = mapResult.value(QStringLiteral("anchors")).toList();
Laurent Montel's avatar
Laurent Montel committed
101
    Q_FOREACH (const QVariant &var, lst) {
Laurent Montel's avatar
Laurent Montel committed
102 103 104
        QMap<QString, QVariant> mapVariant = var.toMap();
        //qDebug()<<" mapVariant"<<mapVariant;

105
        //1) detect if title has a url and title != href
Laurent Montel's avatar
Laurent Montel committed
106 107
        const QString title = mapVariant.value(QStringLiteral("title")).toString();
        const QString href = mapVariant.value(QStringLiteral("src")).toString();
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
        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
128
                    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>");
129 130 131 132 133 134 135
                }
            }
        }
        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
136
                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>");
137 138
                foundScam = true;
            } else if (hostname.contains(QLatin1Char('%'))) { //Hexa value for ip
Laurent Montel's avatar
Laurent Montel committed
139
                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>");
140 141
                foundScam = true;
            } else if (url.toString().contains(QStringLiteral("url?q="))) { //4) redirect url.
Laurent Montel's avatar
Laurent Montel committed
142
                d->mDetails += QLatin1String("<li>") + i18n("This email contains a link (%1) which has a redirection", addWarningColor(url.toString())) + QLatin1String("</li>");
143 144 145 146
                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
147
                    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>");
148 149 150 151 152 153 154
                    foundScam = true;
                }
            }
        }
        //Check shortUrl
        if (!foundScam) {
            if (ScamCheckShortUrl::isShortUrl(url)) {
Laurent Montel's avatar
Laurent Montel committed
155
                d->mDetails += QLatin1String("<li>") + i18n("This email contains a shorturl (%1). It can redirect to another server.", addWarningColor(url.toString())) + QLatin1String("</li>");
156 157 158 159
                foundScam = true;
            }
        }
    }
Laurent Montel's avatar
Laurent Montel committed
160
    if (mapResult.value(QStringLiteral("forms")).toInt() > 0) {
Laurent Montel's avatar
Laurent Montel committed
161
        d->mDetails += QLatin1String("<li></b>") + i18n("Message contains form element. This is often the case in scam emails.") + QLatin1String("</b></li>");
162 163
        foundScam = true;
    }
Laurent Montel's avatar
Laurent Montel committed
164
    d->mDetails += QLatin1String("</ul>");
Laurent Montel's avatar
Laurent Montel committed
165
    //qDebug()<<" d->mDetails "<< d->mDetails;
Laurent Montel's avatar
Laurent Montel committed
166 167 168
    if (foundScam) {
        Q_EMIT messageMayBeAScam();
    }
169
    Q_EMIT resultScanDetection(foundScam);
Laurent Montel's avatar
Laurent Montel committed
170
}
171 172 173 174 175 176 177 178 179

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