Messagelib

searchfullhashjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2016-2022 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 <QUrlQuery>
13 #include <webengineviewer_debug.h>
14 
15 using namespace WebEngineViewer;
16 
17 WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_SearchFullHashJob = true;
18 
19 class WebEngineViewer::SearchFullHashJobPrivate
20 {
21 public:
22  SearchFullHashJobPrivate() = default;
23 
24  Q_REQUIRED_RESULT bool foundExactHash(const QList<QByteArray> &listLongHash);
26  QUrl mUrl;
27  QStringList mDatabaseHashes;
28  QNetworkAccessManager *mNetworkAccessManager = nullptr;
29 };
30 
31 SearchFullHashJob::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 
44 SearchFullHashJob::~SearchFullHashJob() = default;
45 
46 void 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 
52 void 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  */
87  QJsonDocument document = QJsonDocument::fromJson(replyStr);
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  if (info.count() == 1) {
102  const QVariantMap map = info.at(0).toMap();
103  const QString threatTypeStr = map[QStringLiteral("threatType")].toString();
104 
105  // const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString();
106 
107  if (threatTypeStr == QLatin1String("MALWARE")) {
108  const QVariantMap urlMap = map[QStringLiteral("threat")].toMap();
109  QList<QByteArray> hashList;
110  QMap<QString, QVariant>::const_iterator urlMapIt = urlMap.cbegin();
111  const QMap<QString, QVariant>::const_iterator urlMapItEnd = urlMap.cend();
112  hashList.reserve(urlMap.count());
113  for (; urlMapIt != urlMapItEnd; ++urlMapIt) {
114  const QByteArray hashStr = urlMapIt.value().toByteArray();
115  hashList << hashStr;
116  }
117 
118  if (d->foundExactHash(hashList)) {
119  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl);
120  } else {
121  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
122  }
123  const QVariantMap threatEntryMetadataMap = map[QStringLiteral("threatEntryMetadata")].toMap();
124  if (!threatEntryMetadataMap.isEmpty()) {
125  // TODO
126  }
127  } else {
128  qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse threatTypeStr : " << threatTypeStr;
129  }
130  } else {
131  qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse matches multi element : " << info.count();
132  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
133  }
134  }
135  }
136  deleteLater();
137 }
138 
139 bool SearchFullHashJobPrivate::foundExactHash(const QList<QByteArray> &listLongHash)
140 {
141  const QList<QByteArray> lstLongHash = mHashs.keys();
142  for (const QByteArray &ba : lstLongHash) {
143  if (listLongHash.contains(ba)) {
144  return true;
145  }
146  }
147  return false;
148 }
149 
150 void SearchFullHashJob::slotCheckUrlFinished(QNetworkReply *reply)
151 {
152  parse(reply->readAll());
153  reply->deleteLater();
154 }
155 
156 void SearchFullHashJob::setSearchHashs(const QHash<QByteArray, QByteArray> &hash)
157 {
158  d->mHashs = hash;
159 }
160 
161 QByteArray SearchFullHashJob::jsonRequest() const
162 {
163  /*
164  {
165  "client": {
166  "clientId": "yourcompanyname",
167  "clientVersion": "1.5.2"
168  },
169  "clientStates": [
170  "ChAIARABGAEiAzAwMSiAEDABEAE=",
171  "ChAIAhABGAEiAzAwMSiAEDABEOgH"
172  ],
173  "threatInfo": {
174  "threatTypes": ["MALWARE", "SOCIAL_ENGINEERING"],
175  "platformTypes": ["WINDOWS"],
176  "threatEntryTypes": ["URL"],
177  "threatEntries": [
178  {"hash": "WwuJdQ=="},
179  {"hash": "771MOg=="},
180  {"hash": "5eOrwQ=="}
181  ]
182  }
183  }
184  */
185  QVariantMap clientMap;
186  QVariantMap map;
187 
188  clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE"));
189  clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps());
190  map.insert(QStringLiteral("client"), clientMap);
191 
192  // clientStates We can support multi database.
193  QVariantList clientStatesList;
194  for (const QString &str : std::as_const(d->mDatabaseHashes)) {
195  if (!str.isEmpty()) {
196  clientStatesList.append(str);
197  }
198  }
199  map.insert(QStringLiteral("clientStates"), clientStatesList);
200 
201  QVariantMap threatMap;
202  QVariantList platformList;
203  platformList.append(QLatin1String("WINDOWS"));
204  threatMap.insert(QStringLiteral("platformTypes"), platformList);
205 
206  const QVariantList threatTypesList = {QStringLiteral("MALWARE")};
207  threatMap.insert(QStringLiteral("threatTypes"), threatTypesList);
208  const QVariantList threatEntryTypesList = {QStringLiteral("URL")};
209  threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList);
210 
211  QVariantList threatEntriesList;
212 
213  QVariantMap hashUrlMap;
215  while (i.hasNext()) {
216  i.next();
217  hashUrlMap.insert(QStringLiteral("hash"), i.value());
218  }
219  threatEntriesList.append(hashUrlMap);
220 
221  threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList);
222 
223  map.insert(QStringLiteral("threatInfo"), threatMap);
224 
225  const QJsonDocument postData = QJsonDocument::fromVariant(map);
226  const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_SearchFullHashJob ? QJsonDocument::Compact : QJsonDocument::Indented);
227  return baPostData;
228 }
229 
231 {
232  if (!PimCommon::NetworkManager::self()->isOnline()) {
233  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl);
234  deleteLater();
235  } else if (canStart()) {
237  query.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey());
238  QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/fullHashes:find"));
239  safeUrl.setQuery(query);
240  QNetworkRequest request(safeUrl);
241  request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
242 
243  const QByteArray baPostData = jsonRequest();
244  // qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData;
245  Q_EMIT debugJson(baPostData);
246  QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData);
247  connect(reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &SearchFullHashJob::slotError);
248  } else {
249  Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl);
250  deleteLater();
251  }
252 }
253 
254 void SearchFullHashJob::slotError(QNetworkReply::NetworkError error)
255 {
256  auto reply = qobject_cast<QNetworkReply *>(sender());
257  qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString();
258  reply->deleteLater();
259  deleteLater();
260 }
261 
262 bool SearchFullHashJob::canStart() const
263 {
264  return !d->mHashs.isEmpty() && !d->mDatabaseHashes.isEmpty() && !d->mUrl.isEmpty();
265 }
266 
267 void SearchFullHashJob::setDatabaseState(const QStringList &hash)
268 {
269  d->mDatabaseHashes = hash;
270 }
271 
272 void SearchFullHashJob::setSearchFullHashForUrl(const QUrl &url)
273 {
274  d->mUrl = url;
275 }
QString errorString() const const
void errorOccurred(QNetworkReply::NetworkError code)
std::optional< QSqlQuery > query(const QString &queryStatement)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
bool isNull() const const
QVariant toVariant() const const
Q_SCRIPTABLE Q_NOREPLY void start()
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
bool contains(const T &value) const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void reserve(int alloc)
void finished(QNetworkReply *reply)
void deleteLater()
KHEALTHCERTIFICATE_EXPORT QVariant parse(const QByteArray &data)
void sslErrors(QNetworkReply *reply, const QList< QSslError > &errors)
QMap< QString, QVariant > toMap() const const
void setQuery(const QString &query, QUrl::ParsingMode mode)
void ignoreSslErrors(const QList< QSslError > &errors)
int count() const const
QByteArray toJson() const const
QByteArray readAll()
QJsonDocument fromVariant(const QVariant &variant)
QFuture< void > map(Sequence &sequence, MapFunctor function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 03:52:55 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.