Messagelib

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

KDE's Doxygen guidelines are available online.