20 QCoreApplication, QEventLoop |
20 QCoreApplication, QEventLoop |
21 from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply |
21 from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply |
22 |
22 |
23 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
23 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
24 |
24 |
|
25 from .SafeBrowsingThreatList import ThreatList |
|
26 |
25 |
27 |
26 class SafeBrowsingAPIClient(QObject): |
28 class SafeBrowsingAPIClient(QObject): |
27 """ |
29 """ |
28 Class implementing the low level interface for Google Safe Browsing. |
30 Class implementing the low level interface for Google Safe Browsing. |
29 |
31 |
30 @signal networkError(str) emitted to indicate a network error |
32 @signal networkError(str) emitted to indicate a network error |
31 """ |
33 """ |
32 ClientId = "eric6_API_client" |
34 ClientId = "eric6_API_client" |
33 ClientVersion = "1.0.0" |
35 ClientVersion = "2.0.0" |
34 |
36 |
35 GsbUrlTemplate = "https://safebrowsing.googleapis.com/v4/{0}?key={1}" |
37 GsbUrlTemplate = "https://safebrowsing.googleapis.com/v4/{0}?key={1}" |
36 |
38 |
37 networkError = pyqtSignal(str) |
39 networkError = pyqtSignal(str) |
38 |
40 |
45 @param fairUse flag indicating to follow the fair use policy |
47 @param fairUse flag indicating to follow the fair use policy |
46 @type bool |
48 @type bool |
47 @param parent reference to the parent object |
49 @param parent reference to the parent object |
48 @type QObject |
50 @type QObject |
49 """ |
51 """ |
|
52 super(SafeBrowsingAPIClient, self).__init__(parent) |
|
53 |
50 self.__apiKey = apiKey |
54 self.__apiKey = apiKey |
51 self.__fairUse = fairUse |
55 self.__fairUse = fairUse |
52 |
56 |
53 self.__nextRequestNoSoonerThan = QDateTime() |
57 self.__nextRequestNoSoonerThan = QDateTime() |
54 self.__failCount = 0 |
58 self.__failCount = 0 |
|
59 |
|
60 self.__lookupApiCache = {} |
|
61 # Temporary cache used by the lookup API (v4) |
|
62 # key: URL as string |
|
63 # value: dictionary with these entries: |
|
64 # "validUntil": (QDateTime) |
|
65 # "threatInfo": (ThreatList) |
55 |
66 |
56 def setApiKey(self, apiKey): |
67 def setApiKey(self, apiKey): |
57 """ |
68 """ |
58 Public method to set the API key. |
69 Public method to set the API key. |
59 |
70 |
260 |
271 |
261 ####################################################################### |
272 ####################################################################### |
262 ## Methods below implement the 'Lookup API (v4)' |
273 ## Methods below implement the 'Lookup API (v4)' |
263 ####################################################################### |
274 ####################################################################### |
264 |
275 |
265 # TODO: implement the Lookup API (including temporary caching) |
276 def lookupUrl(self, url, platforms): |
|
277 """ |
|
278 Public method to send an URL to Google for checking. |
|
279 |
|
280 @param url URL to be checked |
|
281 @type QUrl |
|
282 @param platforms list of platform types to check against |
|
283 @type list of str |
|
284 @return list of threat list info objects |
|
285 @rtype list of ThreatList |
|
286 """ |
|
287 # sanitize the URL by removing user info and query data |
|
288 url = url.adjusted( |
|
289 QUrl.RemoveUserInfo | QUrl.RemoveQuery | QUrl.RemoveFragment |
|
290 ) |
|
291 urlStr = url.toString() |
|
292 if urlStr in self.__lookupApiCache: |
|
293 if self.__lookupApiCache[urlStr]["validUntil"] > \ |
|
294 QDateTime.currentDateTime(): |
|
295 # cached entry is still valid |
|
296 return self.__lookupApiCache[urlStr]["threatInfo"] |
|
297 else: |
|
298 del self.__lookupApiCache[urlStr] |
|
299 |
|
300 requestBody = { |
|
301 "client": { |
|
302 "clientId": self.ClientId, |
|
303 "clientVersion": self.ClientVersion, |
|
304 }, |
|
305 "threatInfo": { |
|
306 "threatTypes": [ |
|
307 "MALWARE", "SOCIAL_ENGINEERING", "UNWANTED_SOFTWARE", |
|
308 "POTENTIALLY_HARMFUL_APPLICATION", |
|
309 ], |
|
310 "platformTypes": platforms, |
|
311 "threatEntryTypes": ["URL", "EXECUTABLE"], |
|
312 "threatEntries": [ |
|
313 {"url": urlStr}, |
|
314 ], |
|
315 }, |
|
316 } |
|
317 |
|
318 data = QByteArray(json.dumps(requestBody).encode("utf-8")) |
|
319 url = QUrl(self.GsbUrlTemplate.format("threatMatches:find", |
|
320 self.__apiKey)) |
|
321 req = QNetworkRequest(url) |
|
322 req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") |
|
323 reply = WebBrowserWindow.networkManager().post(req, data) |
|
324 |
|
325 while reply.isRunning(): |
|
326 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
|
327 # max. 200 ms processing |
|
328 |
|
329 threats = [] |
|
330 if reply.error() != QNetworkReply.NoError: |
|
331 self.networkError.emit(reply.errorString()) |
|
332 else: |
|
333 res = json.loads(str(reply.readAll(), "utf-8")) |
|
334 if res and "matches" in res: |
|
335 cacheDuration = 0 |
|
336 for match in res["matches"]: |
|
337 threatInfo = ThreatList( |
|
338 match["threatType"], |
|
339 match["platformType"], |
|
340 match["threatEntryType"], |
|
341 ) |
|
342 threats.append(threatInfo) |
|
343 if "cacheDuration" in match: |
|
344 cacheDurationSec = int( |
|
345 match["cacheDuration"].strip().rstrip("s") |
|
346 .split(".")[0]) |
|
347 if cacheDurationSec > cacheDuration: |
|
348 cacheDuration = cacheDurationSec |
|
349 if cacheDuration > 0 and bool(threats): |
|
350 validUntil = QDateTime.currentDateTime().addSecs( |
|
351 cacheDuration) |
|
352 self.__lookupApiCache[urlStr] = { |
|
353 "validUntil": validUntil, |
|
354 "threatInfo": threats |
|
355 } |
|
356 |
|
357 reply.deleteLater() |
|
358 return threats |
266 |
359 |
267 ####################################################################### |
360 ####################################################################### |
268 ## Methods below implement global (class wide) functionality |
361 ## Methods below implement global (class wide) functionality |
269 ####################################################################### |
362 ####################################################################### |
270 |
363 |