Messagelib

searchfullhashjob.cpp
1/*
2 SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "searchfullhashjob.h"
8
9#include <PimCommon/NetworkManager>
10#include <QJsonDocument>
11#include <QNetworkAccessManager>
12#include <QUrlQuery>
13#include <webengineviewer_debug.h>
14
15using namespace WebEngineViewer;
16
17WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_SearchFullHashJob = true;
18
19class WebEngineViewer::SearchFullHashJobPrivate
20{
21public:
22 SearchFullHashJobPrivate() = default;
23
24 [[nodiscard]] bool foundExactHash(const QList<QByteArray> &listLongHash);
26 QUrl mUrl;
27 QStringList mDatabaseHashes;
28 QNetworkAccessManager *mNetworkAccessManager = nullptr;
29};
30
31SearchFullHashJob::SearchFullHashJob(QObject *parent)
32 : QObject(parent)
33 , d(new SearchFullHashJobPrivate)
34{
35 d->mNetworkAccessManager = new QNetworkAccessManager(this);
36 d->mNetworkAccessManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
37 d->mNetworkAccessManager->setStrictTransportSecurityEnabled(true);
38 d->mNetworkAccessManager->enableStrictTransportSecurityStore(true);
39
40 connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &SearchFullHashJob::slotCheckUrlFinished);
41 connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &SearchFullHashJob::slotSslErrors);
42}
43
44SearchFullHashJob::~SearchFullHashJob() = default;
45
46void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)
47{
48 qCDebug(WEBENGINEVIEWER_LOG) << " void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)" << error.count();
49 reply->ignoreSslErrors(error);
50}
51
52void SearchFullHashJob::parse(const QByteArray &replyStr)
53{
54 /*
55
56 {
57 "matches": [{
58 "threatType": "MALWARE",
59 "platformType": "WINDOWS",
60 "threatEntryType": "URL",
61 "threat": {
62 "hash": "WwuJdQx48jP-4lxr4y2Sj82AWoxUVcIRDSk1PC9Rf-4="
63 },
64 "threatEntryMetadata": {
65 "entries": [{
66 "key": "bWFsd2FyZV90aHJlYXRfdHlwZQ==", // base64-encoded "malware_threat_type"
67 "value": "TEFORElORw==" // base64-encoded "LANDING"
68 }]
69 },
70 "cacheDuration": "300.000s"
71 }, {
72 "threatType": "SOCIAL_ENGINEERING",
73 "platformType": "WINDOWS",
74 "threatEntryType": "URL",
75 "threat": {
76 "hash": "771MOrRPMn6xPKlCrXx_CrR-wmCk0LgFFoSgGy7zUiA="
77 },
78 "threatEntryMetadata": {
79 "entries": []
80 },
81 "cacheDuration": "300.000s"
82 }],
83 "minimumWaitDuration": "300.000s",
84 "negativeCacheDuration": "300.000s"
85 }
86 */
88 if (document.isNull()) {
89 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
90 } else {
91 const QVariantMap answer = document.toVariant().toMap();
92 if (answer.isEmpty()) {
93 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl);
94 return;
95 } else {
96 const QVariantList info = answer.value(QStringLiteral("matches")).toList();
97 // TODO
98 // const QString minimumWaitDuration = answer.value(QStringLiteral("minimumWaitDuration")).toString();
99 // const QString negativeCacheDuration = answer.value(QStringLiteral("negativeCacheDuration")).toString();
100 // Implement multi match ?
101 const auto numberOfInfo{info.count()};
102 if (numberOfInfo == 1) {
103 const QVariantMap map = info.at(0).toMap();
104 const QString threatTypeStr = map[QStringLiteral("threatType")].toString();
105
106 // const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString();
107
108 if (threatTypeStr == QLatin1StringView("MALWARE")) {
109 const QVariantMap urlMap = map[QStringLiteral("threat")].toMap();
110 QList<QByteArray> hashList;
113 hashList.reserve(urlMap.count());
114 for (; urlMapIt != urlMapItEnd; ++urlMapIt) {
115 const QByteArray hashStr = urlMapIt.value().toByteArray();
116 hashList << hashStr;
117 }
118
119 if (d->foundExactHash(hashList)) {
120 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl);
121 } else {
122 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
123 }
124 const QVariantMap threatEntryMetadataMap = map[QStringLiteral("threatEntryMetadata")].toMap();
125 if (!threatEntryMetadataMap.isEmpty()) {
126 // TODO
127 }
128 } else {
129 qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse threatTypeStr : " << threatTypeStr;
130 }
131 } else if (numberOfInfo == 0) {
132 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl);
133 } else {
134 qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse matches multi element : " << info.count();
135 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
136 }
137 }
138 }
139 deleteLater();
140}
141
142bool SearchFullHashJobPrivate::foundExactHash(const QList<QByteArray> &listLongHash)
143{
144 const QList<QByteArray> lstLongHash = mHashs.keys();
145 for (const QByteArray &ba : lstLongHash) {
146 if (listLongHash.contains(ba)) {
147 return true;
148 }
149 }
150 return false;
151}
152
153void SearchFullHashJob::slotCheckUrlFinished(QNetworkReply *reply)
154{
155 parse(reply->readAll());
156 reply->deleteLater();
157}
158
159void SearchFullHashJob::setSearchHashs(const QHash<QByteArray, QByteArray> &hash)
160{
161 d->mHashs = hash;
162}
163
164QByteArray SearchFullHashJob::jsonRequest() const
165{
166 /*
167 {
168 "client": {
169 "clientId": "yourcompanyname",
170 "clientVersion": "1.5.2"
171 },
172 "clientStates": [
173 "ChAIARABGAEiAzAwMSiAEDABEAE=",
174 "ChAIAhABGAEiAzAwMSiAEDABEOgH"
175 ],
176 "threatInfo": {
177 "threatTypes": ["MALWARE", "SOCIAL_ENGINEERING"],
178 "platformTypes": ["WINDOWS"],
179 "threatEntryTypes": ["URL"],
180 "threatEntries": [
181 {"hash": "WwuJdQ=="},
182 {"hash": "771MOg=="},
183 {"hash": "5eOrwQ=="}
184 ]
185 }
186 }
187 */
188 QVariantMap clientMap;
189 QVariantMap map;
190
191 clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE"));
192 clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps());
193 map.insert(QStringLiteral("client"), clientMap);
194
195 // clientStates We can support multi database.
196 QVariantList clientStatesList;
197 for (const QString &str : std::as_const(d->mDatabaseHashes)) {
198 if (!str.isEmpty()) {
199 clientStatesList.append(str);
200 }
201 }
202 map.insert(QStringLiteral("clientStates"), clientStatesList);
203
204 QVariantMap threatMap;
205 QVariantList platformList;
206 platformList.append(QLatin1StringView("WINDOWS"));
207 threatMap.insert(QStringLiteral("platformTypes"), platformList);
208
209 const QVariantList threatTypesList = {QStringLiteral("MALWARE")};
210 threatMap.insert(QStringLiteral("threatTypes"), threatTypesList);
211 const QVariantList threatEntryTypesList = {QStringLiteral("URL")};
212 threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList);
213
214 QVariantList threatEntriesList;
215
216 QVariantMap hashUrlMap;
218 while (i.hasNext()) {
219 i.next();
220 hashUrlMap.insert(QStringLiteral("hash"), i.value());
221 }
223
224 threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList);
225
226 map.insert(QStringLiteral("threatInfo"), threatMap);
227
229 const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_SearchFullHashJob ? QJsonDocument::Compact : QJsonDocument::Indented);
230 return baPostData;
231}
232
233void SearchFullHashJob::start()
234{
235 if (!PimCommon::NetworkManager::self()->isOnline()) {
236 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl);
237 deleteLater();
238 } else if (canStart()) {
240 query.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey());
241 QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/fullHashes:find"));
242 safeUrl.setQuery(query);
243 QNetworkRequest request(safeUrl);
244 request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
245
246 const QByteArray baPostData = jsonRequest();
247 // qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData;
248 Q_EMIT debugJson(baPostData);
249 QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData);
250 connect(reply, &QNetworkReply::errorOccurred, this, &SearchFullHashJob::slotError);
251 } else {
252 Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl);
253 deleteLater();
254 }
255}
256
257void SearchFullHashJob::slotError(QNetworkReply::NetworkError error)
258{
260 qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString();
261 reply->deleteLater();
262 deleteLater();
263}
264
265bool SearchFullHashJob::canStart() const
266{
267 return !d->mHashs.isEmpty() && !d->mDatabaseHashes.isEmpty() && !d->mUrl.isEmpty();
268}
269
270void SearchFullHashJob::setDatabaseState(const QStringList &hash)
271{
272 d->mDatabaseHashes = hash;
273}
274
275void SearchFullHashJob::setSearchFullHashForUrl(const QUrl &url)
276{
277 d->mUrl = url;
278}
279
280#include "moc_searchfullhashjob.cpp"
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QList< Key > keys() const const
QString errorString() const const
QByteArray readAll()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonDocument fromVariant(const QVariant &variant)
bool isNull() const const
QVariant toVariant() const const
bool contains(const T &value) const const
void reserve(int alloc)
void finished(QNetworkReply *reply)
void sslErrors(QNetworkReply *reply, const QList< QSslError > &errors)
void errorOccurred(QNetworkReply::NetworkError code)
void ignoreSslErrors(const QList< QSslError > &errors)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void deleteLater()
T qobject_cast(QObject *object)
QObject * sender() const const
QFuture< void > map(Sequence &sequence, MapFunctor function)
QMap< QString, QVariant > toMap() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:37:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.