scamdetectionwebengine.cpp 7.32 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 89 90 91 92 93 94 95 96
        page->runJavaScript(MessageViewer::WebEngineScript::findAllAnchors(), invoke(this, &ScamDetectionWebEngine::handleScanPage));
    }
}

void ScamDetectionWebEngine::handleScanPage(const QVariant &result)
{
    bool foundScam = false;
    d->mDetails.clear();
    d->mDetails = QLatin1String("<b>") + i18n("Details:") + QLatin1String("</b><ul>");
97
    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
98
    const QList<QVariant> lst = result.toList();
Laurent Montel's avatar
Laurent Montel committed
99
    Q_FOREACH (const QVariant &var, lst) {
Laurent Montel's avatar
Laurent Montel committed
100 101 102
        QMap<QString, QVariant> mapVariant = var.toMap();
        //qDebug()<<" mapVariant"<<mapVariant;

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

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