WebBrowser/SafeBrowsing/SafeBrowsingAPIClient.py

changeset 6233
a64b986abb54
parent 6230
77a1b22c8a1f
child 6234
fb1f9e681848
equal deleted inserted replaced
6232:d2eac9616a92 6233:a64b986abb54
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

eric ide

mercurial