scamdetectionwebengine.cpp 7.53 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 "webengine/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 80 81 82 83 84

ScamCheckShortUrl *ScamDetectionWebEngine::scamCheckShortUrl() const
{
    return MessageViewer::ScamCheckShortUrlManager::self()->scamCheckShortUrl();
}

Laurent Montel's avatar
Laurent Montel committed
85 86 87
void ScamDetectionWebEngine::scanPage(QWebEnginePage *page)
{
    if (MessageViewer::MessageViewerSettings::self()->scamDetectionEnabled()) {
Laurent Montel's avatar
Laurent Montel committed
88
        page->runJavaScript(MessageViewer::WebEngineScript::findAllAnchorsAndForms(), invoke(this, &ScamDetectionWebEngine::handleScanPage));
Laurent Montel's avatar
Laurent Montel committed
89 90 91 92 93 94
    }
}

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

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

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

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